import datetime
from django.utils.safestring import mark_safe
from django.db import models
from django.db.models import F, ExpressionWrapper, DecimalField
from django.core.exceptions import ValidationError
from django.utils import timezone
from base import models as baseModels
from production import models as productionModels
from datetime import datetime
from django.db import transaction
import os
from django.db.models import Sum
def validate_file_size(value):
    """验证文件大小不超过5MB"""
    filesize = value.size
    if filesize > 5 * 1024 * 1024:  # 5MB
        raise ValidationError("文件大小不能超过5MB")
    allowed_extensions = ['.pdf', '.img', '.jpg', '.webp', '.docx', '.doc']
    ext = os.path.splitext(value.name)[1].lower()
    if ext not in allowed_extensions:
        raise ValidationError('只允许上传常规文件')
    return value

# Create your models here.
class StoreRawOrderModel(models.Model):
    '''物料入库'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='入库日期', default=timezone.now)
    TYPE_CHOICES = [
        ("1", "入库单"),
        ("0", "期初入库单"),
    ]
    type = models.CharField(max_length=50, verbose_name='类型', default='1', choices=TYPE_CHOICES)
    INVOICE_CHOICES = [
        ("1", "没有发票"),
        ("0", "有发票"),
    ]
    invoice = models.CharField(max_length=50, verbose_name='类型', default='1', choices=INVOICE_CHOICES)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料入库管理'
        verbose_name_plural = '原料入库管理'  # 自定义复数形式，与单数相同

    @property
    def total_amount(self):
        """计算入库单总金额（含税）"""
        # 使用 F() 表达式进行数据库层面的计算（支持字段乘法）
        return self.storerawdetailmodel_set.filter(parent__isnull=True).aggregate(
            total=Sum(
                ExpressionWrapper(
                    F('price') * F('store_num') * (1 + F('tax') * 0.01),  # 正确的字段计算方式
                    output_field=DecimalField()
                )
            )
        )['total'] or 0

    def __str__(self):

        return '|'.join([self.orderId, f"总含税金额：{self.total_amount:.2f}", self.supplier.name])
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期小于入库日期！"})

            if not self.id: #新增
                if self.supplier.state !='1':
                    raise ValidationError({"supplier": f"{self.supplier.name}供应商不能停用，无法保存！"})
                if not self.orderId:


                    id_date=datetime.now()
                    if self.date:
                       id_date=self.date
                    orderIdList=StoreRawOrderModel.objects.filter(orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',flat=True).first()
                    print(orderIdList)
                    if orderIdList:
                        try:
                            result=orderIdList[:7]+str(int(orderIdList[7:])+1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result=id_date.strftime('%Y%m%d')+'0001'
                    # print(raw)
                    self.orderId=result

            try:
                 if len(self.orderId) == 12:
                   int(self.orderId[7:])
                 else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                 raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

class StoreRawOrderFileModel(models.Model):
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 先定义动态 upload_to 函数（关键）
    def dynamic_upload_path(instance, filename):
        """
        动态生成文件上传路径
        格式: uploads/files/report/[用户ID]/[应用标签]/[模型名称]/[文件名]
        """
        # # 获取模型元数据
        # code = instance.code  # 应用标签
        #
        # # 获取用户ID（确保模型有user字段关联）
        # user_id = instance.user.id

        # 拼接路径（将特殊字符替换为下划线，避免路径错误）
        return f"uploads/files/store/raw/{instance.pk}/{filename}"
    file = models.ImageField(upload_to=dynamic_upload_path, validators=[validate_file_size],
                                   verbose_name='附件')


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '附件'
        verbose_name_plural = '附件'  # 自定义复数形式，与单数相同




class StoreRawDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        baseModels.RawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
        related_name = 'store_details'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    location = models.ForeignKey(
        baseModels.LocationRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    batch_number = models.CharField(max_length=50, verbose_name='批号', default=None, null=False,  # 数据库层面不允许为NULL
                               blank=False)
    # 将原来的standard字符字段改为外键关联到StandardModel
    unit = models.ForeignKey(
        baseModels.UnitModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='单位',
        # related_name = 'store_details_unit'  # 自定义反向关联名（推荐使用）
    )

    store_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)
    price = models.DecimalField(verbose_name='单价', max_digits=20, decimal_places=13, default=0)
    tax = models.DecimalField(verbose_name='税率%', max_digits=3, decimal_places=0, default=9)
    remaining_num=models.DecimalField(verbose_name='剩余数量', max_digits=10,decimal_places=2,default=0)
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    # parebt
    parent = models.ForeignKey(
       'self',
        on_delete=models.PROTECT,
        null=True,  # 数据库层面不允许为NULL
        blank=True,
        default=None,
        verbose_name='父级',
    )
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料入库明细'
        verbose_name_plural = '原料入库明细'  # 自定义复数形式，与单数相同
        # 自定义权限：格式为("权限代码", "权限描述")
        permissions = [
            # ("can_approve_order", "能审核入库单"),  # 审核权限
            # ("can_export_order", "能导出入库单数据"),  # 导出权限
            ("view_subledger_storerawdetailmodel", "能查看明细账"),  # 统计查看权限
        ]


    def __str__(self):
        result='<'+'>  <'.join([self.raw.name, self.batch_number, '剩'+str(self.remaining_num)+str(self.unit.unit),self.location.name ])+'>'
        return result
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.store_order.supplier.raws.all()

            if not self.id: #新增
                if self.raw.state !='1':
                    raise ValidationError({"raw": f"{self.raw.name}物料不能停用，无法保存！"})
                if self.store_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.store_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                self.remaining_num=self.store_num
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            else:
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                old_store_num=StoreRawDetailModel.objects.filter(id=self.id).values_list('store_num').first()

                self.remaining_num=self.store_num-old_store_num[0]+self.remaining_num
                if self.remaining_num < 0:
                    raise ValidationError({"store_num": f"剩余数量不能小于0，无法修改保存！改后剩余数{self.remaining_num}"})
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).exclude(id=self.id if self.id else None).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            if self.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.store_order.supplier.name}不供应{self.raw.name}物料，无法保存！"})

        except Exception as e:
            raise ValidationError(e)

            # raise  ValidationError({"raw": f"{e}错误！"})
class TransferRawOrderModel(models.Model):
    '''物料调拨'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='调拨日期', default=timezone.now)


    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料调拨管理'
        verbose_name_plural = '原料调拨管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def complete(self):
        """完成调拨单"""
        # 执行调拨操作
        for detail in self.transferrawdetailmodel_set.all():
            # 减少源货位库存
            from_store = detail.from_store_detail
            from_store.remaining_num -= detail.transfer_num
            if from_store.remaining_num < 0:
                raise ValidationError(f"源货位库存不足: {from_store}")
            from_store.save()

            # 增加目标货位库存
            # 检查目标货位类型，如果是货位，确保没有其他库存
            if detail.to_location.type == '0':  # 货位
                existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=detail.to_store_detail.id if detail.to_store_detail else None
                ).exists()
                if existing:
                    raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")

            if detail.to_store_detail:
                # 更新现有库存记录
                to_store = detail.to_store_detail
                to_store.remaining_num += detail.transfer_num
                to_store.save()
            else:
                # 创建新的库存记录
                to_store = StoreRawDetailModel(
                    store_order=None,  # 调拨产生的库存没有对应的入库单
                    raw=detail.from_store_detail.raw,
                    location=detail.to_location,
                    batch_number=detail.from_store_detail.batch_number,
                    unit=detail.from_store_detail.unit,
                    store_num=detail.transfer_num,
                    remaining_num=detail.transfer_num
                )
                to_store.save()
                detail.to_store_detail = to_store
                detail.save()


        self.save()

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 执行调拨操作
                for detail in self.transferrawdetailmodel_set.all():
                    # 减少源货位库存
                    from_store = detail.from_store_detail
                    if from_store.remaining_num-detail.transfer_num < 0:
                        raise ValidationError(f"源货位库存不足: {from_store}")
                    # 检查目标货位类型，如果是货位，确保没有其他库存
                    if detail.to_location.type == '0':  # 货位
                        existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                            id=detail.to_store_detail.id if detail.to_store_detail else None
                        ).exists()
                        if existing:
                            raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")


            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = TransferRawOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})

        except Exception as e:
            raise ValidationError(e)
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # # 检查生产品种状态，不允许删除已保存品种的出库单
        # if self.product.state == '1':
        #     raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.transferrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                # store_raw_detail.save()
                detail.delete()
                #删除对应的入库数据
                # detail.to_store_detail.delete()
            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            # details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.transferrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                    store_raw_detail.remaining_num += detail.transfer_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    # store_raw_detail.save()
                    # 要先删除明细
                    detail.delete()
                    # # 删除对应的入库数据
                    # detail.to_store_detail.delete()
                # 删除明细
                # details.delete()

            # 批量删除主单
            queryset.delete()
class TransferRawDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    transfer_order = models.ForeignKey(
        TransferRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    from_store_detail = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨前原料',
        related_name = 'store_details_before'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    to_location = models.ForeignKey(
        baseModels.LocationRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details_transfer'  # 自定义反向关联名（推荐使用）
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    to_store_detail = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨后原料',
        related_name='store_details_after'  # 自定义反向关联名（推荐使用）
    )

    transfer_num = models.DecimalField(verbose_name='调拨数量', max_digits=10,decimal_places=2)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料调拨明细'
        verbose_name_plural = '原料调拨明细'  # 自定义复数形式，与单数相同




    def clean(self):
        # 验证源和目标不能相同
        if self.from_store_detail.location == self.to_location:
            raise ValidationError({"to_location":"源货位和目标货位不能相同"})

        # 验证调拨数量
        if not self.transfer_num or self.transfer_num <= 0:
            raise ValidationError("调拨数量必须大于0")

        # 验证源货位有足够库存
        if self.from_store_detail.remaining_num < self.transfer_num:
            raise ValidationError("调拨数量不能大于源货位剩余数量")

        # 验证目标货位类型限制
        if self.to_location.type == '0':  # 货位
            # 检查目标货位是否已有其他库存
            try:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=self.to_store_detail.id if self.to_store_detail else None
                ).exists()
            except:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exists()
            if existing:
                raise ValidationError("目标货位为库位，已有其他货物，不能再调入")

        # # 如果指定了目标库存记录，验证是否匹配
        # if self.to_store_detail and self.to_store_detail.location != self.to_location:
        #     raise ValidationError("目标库存记录与目标货位不匹配")
        #
        # if self.to_store_detail and self.to_store_detail.raw != self.from_store_detail.raw:
        #     raise ValidationError("目标库存记录与源库存记录的物料不同")

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        # self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_transfer_num = 0
            if self.pk:  # 修改操作
                old_transfer_raw = TransferRawDetailModel.objects.select_for_update().get(id=self.id)
                old_transfer_num = old_transfer_raw.transfer_num
            if self.from_store_detail_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.transfer_num + old_transfer_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()
            try:
                if self.to_store_detail :
                    # 更新现有库存记录
                    to_store = self.to_store_detail
                    to_store.remaining_num = to_store.remaining_num+self.transfer_num-old_transfer_num
                    to_store.save()

            except:
                # 创建新的库存记录
                to_store = StoreRawDetailModel(
                    store_order=self.from_store_detail.store_order,  # 调拨产生的库存没有对应的入库单
                    raw=self.from_store_detail.raw,
                    location=self.to_location,
                    batch_number=self.from_store_detail.batch_number,
                    unit=self.from_store_detail.unit,
                    store_num=self.transfer_num,
                    remaining_num=self.transfer_num,
                    parent=self.from_store_detail
                )
                to_store.save()
                self.to_store_detail = to_store


            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.transfer_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()


            # 执行实际删除操作
            super().delete(*args, **kwargs)
            self.to_store_detail.delete()
    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            needdels=[]
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update     ().get(id=detail.from_store_detail_id)
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！{detail.to_store_detail}")
                store_raw_detail.save()
                needdels.append(detail.to_store_detail)


            # 批量删除记录
            queryset.delete()
            for needdel in needdels:
                needdel.delete()
class LossRawOrderModel(models.Model):
    '''报损管理'''
    orderId = models.CharField(max_length=50, verbose_name='报损单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报损日期', default=timezone.now)

    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料报损管理'
        verbose_name_plural = '原料报损管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:


                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = LossRawOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.lossrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.lossrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.loss_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class LossRawDetailModel(models.Model):
    '''报损单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    loss_order = models.ForeignKey(
        LossRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报损单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'loss_details'  # 自定义反向关联名（推荐使用）
    )

    loss_num = models.DecimalField(verbose_name='报损数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料报损明细'
        verbose_name_plural = '原料报损明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws=self.loss_order.customer.raws.all()
            old_loss_num=0
            if not self.id: #新增

                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"loss_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_loss_raw=LossRawDetailModel.objects.get(id=self.id)
                old_loss_num=old_loss_raw.loss_num



            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.loss_num+old_loss_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_loss_num = 0
            if self.id:  # 修改操作
                old_loss_raw = LossRawDetailModel.objects.select_for_update().get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.loss_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SurplusRawOrderModel(models.Model):
    '''报溢管理'''
    orderId = models.CharField(max_length=50, verbose_name='报溢单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报溢日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料报溢管理'
        verbose_name_plural = '原料报溢管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SurplusRawOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.surplusrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.surplusrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.surplus_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class SurplusRawDetailModel(models.Model):
    '''报溢单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    surplus_order = models.ForeignKey(
        SurplusRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报溢单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
        related_name='surplus_details'  # 自定义反向关联名（推荐使用）
    )

    surplus_num = models.DecimalField(verbose_name='报溢数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料报溢明细'
        verbose_name_plural = '原料报溢明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.surplus_num or self.surplus_num < 0:
                raise ValidationError({"surplus_num": f"数量不能小于0，无法保存！"})
            old_surplus_num = 0
            if self.id:  # 修改
                old_surplus_raw = SurplusRawDetailModel.objects.get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_surplus_num = 0
            if self.id:  # 修改操作
                old_surplus_raw = SurplusRawDetailModel.objects.select_for_update().get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.surplus_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class SellRawOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料销售管理'
        verbose_name_plural = '原料销售管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于销售日期！"})

            if not self.id:  # 新增
                if self.customer.state != '1':
                    raise ValidationError({"customer": f"{self.customer.name}客户不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SellRawOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.sell_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellRawDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sell_order = models.ForeignKey(
        SellRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'sell_details'  # 自定义反向关联名（推荐使用）
    )

    sell_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料销售明细'
        verbose_name_plural = '原料销售明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.sell_order.customer.raws.all()
            old_sell_num=0
            if not self.id: #新增

                if self.sell_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sell_order.customer.name}客户不能停用，无法保存！"})
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sell_raw=SellRawDetailModel.objects.get(id=self.id)
                old_sell_num=old_sell_raw.sell_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sell_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.sell_num+old_sell_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sell_num = 0
            if self.id:  # 修改操作
                old_sell_raw = SellRawDetailModel.objects.select_for_update().get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.sell_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundRawOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料出库管理'
        verbose_name_plural = '原料出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundRawOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backrawordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundRawDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'outbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料出库明细'
        verbose_name_plural = '原料出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundRawDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundRawDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backrawordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backrawdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundRawDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

            class OutboundRawOrderModel(models.Model):
                '''出库管理'''
                orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,
                                           # 数据库层面不允许为NULL
                                           blank=True, unique=True)
                date = models.DateField(verbose_name='出库日期', default=timezone.now)
                # 将原来的standard字符字段改为外键关联到StandardModel
                product = models.ForeignKey(
                    productionModels.ProductOrderModel,
                    on_delete=models.PROTECT,
                    null=False,  # 数据库层面不允许为NULL
                    blank=False,
                    verbose_name='生产品种',
                )
                # 创建时间：首次保存时自动设置为当前时间，之后不会更新
                # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

                create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
                # 更新时间：每次保存（包括首次）时自动更新为当前时间
                update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

                class Meta:
                    verbose_name = '原料出库管理'
                    verbose_name_plural = '原料出库管理'  # 自定义复数形式，与单数相同

                def __str__(self):
                    return self.orderId

                def clean(self):
                    """模型验证方法，用于检查保存条件"""
                    try:
                        if not self.product:
                            raise ValidationError({"product": f"没有选择生产品种！"})
                        if self.product.date > self.date:
                            raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

                        if not self.id:  # 新增
                            if self.product.state == '1':
                                raise ValidationError(
                                    {"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                            if not self.orderId:

                                id_date = datetime.now()
                                if self.date:
                                    id_date = self.date
                                orderIdList = OutboundRawOrderModel.objects.filter(
                                    orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by(
                                    '-orderId').values_list('orderId',
                                                            flat=True).first()

                                if orderIdList:
                                    try:
                                        result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                                    except:
                                        raise ValidationError({"orderId": '订单编号检验失败'})
                                else:
                                    result = id_date.strftime('%Y%m%d') + '0001'
                                # print(raw)
                                self.orderId = result

                        try:
                            if len(self.orderId) == 12:
                                int(self.orderId[7:])
                            else:
                                raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
                        except:
                            raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




                    except Exception as e:

                        raise ValidationError(e)

                def delete(self, *args, **kwargs):
                    """重写删除方法，删除主单时级联删除明细细并恢复库存"""
                    # 检查生产品种状态，不允许删除已保存品种的出库单
                    if self.product.state == '1':
                        raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

                    # 使用事务确保所有操作的原子性
                    with transaction.atomic():
                        # 1. 获取当前出库单关联的所有明细
                        details = self.outboundrawdetailmodel_set.all()

                        # 2. 遍历明细，恢复库存
                        for detail in details:
                            # 锁定库存记录录防止并发问题
                            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                            # 恢复库存
                            store_raw_detail.remaining_num += detail.outbound_num
                            store_raw_detail.save()

                        # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
                        details.delete()

                        # 4. 最后删除主单
                        super().delete(*args, **kwargs)

                @classmethod
                def bulk_delete(cls, queryset):
                    """批量删除出库单并恢复库存"""
                    with transaction.atomic():
                        for order in queryset:
                            # 检查状态
                            if order.product.state == '1':
                                raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                            # 删除时要查看是否有退库的物料
                            backs = order.product.backrawordermodel_set.all()
                            if backs:
                                raise ValidationError(f"有退库，不能删除出库记录！")
                            # 处理关联明细，恢复库存
                            details = order.outboundrawdetailmodel_set.all()
                            for detail in details:
                                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                                store_raw_detail.remaining_num += detail.outbound_num
                                store_raw_detail.save()

                            # 删除明细
                            details.delete()

                        # 批量删除主单
                        queryset.delete()
class OtherOutboundRawOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='出库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True)


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料其他出库管理'
        verbose_name_plural = '原料其他出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OtherOutboundRawOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.otheroutboundrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:


                # 处理关联明细，恢复库存
                details = order.otheroutboundrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class OtherOutboundRawDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OtherOutboundRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'otheroutbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)



    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料其他出库明细'
        verbose_name_plural = '原料其他出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})

            old_out_num=0
            if  self.id: #修改
                old_out_raw=OtherOutboundRawDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''


        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OtherOutboundRawDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class OutboundRawOfIntermediateProductOrderModel(models.Model):
    '''出库管理(中间品生产)'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料出库管理(中间品)'
        verbose_name_plural = '原料出库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundRawOfIntermediateProductOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundrawofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backrawofintermediateproductordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundrawofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundRawOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundRawOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'outbound_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料出库明细(中间品)'
        verbose_name_plural = '原料出库明细(中间品)'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundRawOfIntermediateProductDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundRawOfIntermediateProductDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backrawofintermediateproductordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backrawofintermediateproductdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundRawOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class BackRawOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料退库管理'
        verbose_name_plural = '原料退库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundrawordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backrawdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundrawdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackRawDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackRawOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackRawDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'back_details'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料退库明细'
        verbose_name_plural = '原料退库明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundrawordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundrawdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackRawDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackRawDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreRawDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackRawDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class BackRawOfIntermediateProductOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料退库管理(中间品)'
        verbose_name_plural = '原料退库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundrawofintermediateproductordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backrawofintermediateproductdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundrawofintermediateproductdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackRawOfIntermediateProductDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackRawOfIntermediateProductOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backrawofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backrawofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackRawOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackRawOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'back_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料退库明细(中间品)'
        verbose_name_plural = '原料退库明细(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundrawofintermediateproductordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundrawofintermediateproductdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackRawOfIntermediateProductDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackRawOfIntermediateProductDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreRawDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackRawOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class SellBackRawOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料销售退货管理'
        verbose_name_plural = '原料销售退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                sell_orders = self.customer.sellrawordermodel_set.all()
                if not sell_orders.exists():
                    raise ValidationError({"customer": f"该客户没有对应的销售记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.sellbackrawdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的销售记录
                            material_sells = []
                            for order in sell_orders:
                                material_sells.extend(
                                    order.sellrawdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"customer": f"物料{detail.raw.raw.name}没有对应的销售记录，无法退库！"})

                            # 计算该物料的总销售数量
                            total_sell = sum(item.sell_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = SellBackRawDetailModel.objects.filter(
                                sellback_order__customer=self.customer,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.sellback_num for item in existing_back)

                            # 验证退库数量
                            if detail.sellback_num <= 0:
                                raise ValidationError({"customer": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.sellback_num > total_sell:
                                raise ValidationError({
                                                          "customer": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总销售：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.sell_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "customer": f"退库日期不能早于物料{detail.raw.raw.name}的销售日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = SellBackRawOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellbackrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellbackrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.sellback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellBackRawDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sellback_order = models.ForeignKey(
        SellBackRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'sellback_details'  # 自定义反向关联名（推荐使用）
    )

    sellback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料销售退货明细'
        verbose_name_plural = '原料销售退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.sellback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.sellback_order.customer.raws.all()
            old_sellback_num=0
            if not self.id: #新增

                if self.sellback_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sellback_order.customer.name}客户不能停用，无法保存！"})
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sellback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sellback_raw=SellBackRawDetailModel.objects.get(id=self.id)
                old_sellback_num=old_sellback_raw.sellback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            sell_orders = self.sellback_order.customer.sellrawordermodel_set.all()
            material_sells = []
            for order in sell_orders:
                material_sells.extend(
                    order.sellrawdetailmodel_set.filter(raw=self.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name} 物料{self.raw.raw.name}没有对应的销售记录，无法退库！"})
            else:
                # 计算总销售量
                total_sell = sum(item.sell_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = SellBackRawDetailModel.objects.filter(
                    sellback_order__customer=self.sellback_order.customer,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.sellback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.sellback_num > total_sell:
                    raise ValidationError({
                                              "sellback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.sellback_num}，总销售：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.sell_order.date for item in material_sells)
                if self.sellback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "sellback_num": f"退库日期不能早于物料{self.raw.raw.name}的销售日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.sellback_num-old_sellback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sellback_num = 0
            if self.id:  # 修改操作
                old_sellback_raw = SellBackRawDetailModel.objects.select_for_update().get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.sellback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class StoreBackRawOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='入库退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='入库退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierRawModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '原料入库退货管理'
        verbose_name_plural = '原料入库退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择要退货的供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                store_orders = self.supplier.storerawordermodel_set.all()
                if not store_orders.exists():
                    raise ValidationError({"supplier": f"该供应商没有对应的入库记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.storebackrawdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的入库记录
                            material_sells = []
                            for order in store_orders:
                                material_sells.extend(
                                    order.storerawdetailmodel_set.filter(raw=detail.raw.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"supplier": f"物料{detail.raw.raw.name}没有对应的入库记录，无法退库！"})

                            # 计算该物料的总入库数量
                            total_sell = sum(item.store_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = StoreBackRawDetailModel.objects.filter(
                                storeback_order__supplier=self.supplier,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.storeback_num for item in existing_back)

                            # 验证退库数量
                            if detail.storeback_num <= 0:
                                raise ValidationError({"supplier": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.storeback_num > total_sell:
                                raise ValidationError({
                                                          "supplier": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总入库：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.store_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "supplier": f"退库日期不能早于物料{detail.raw.raw.name}的入库日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = StoreBackRawOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.storebackrawdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.storebackrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.storeback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class StoreBackRawDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    storeback_order = models.ForeignKey(
        StoreBackRawOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreRawDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
        related_name = 'store_back_details'  # 自定义反向关联名（推荐使用）
    )

    storeback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='原料入库退货明细'
        verbose_name_plural = '原料入库退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.storeback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.storeback_order.supplier.raws.all()
            old_storeback_num=0
            if not self.id: #新增

                if self.storeback_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.storeback_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})
                old_storeback_raw=StoreBackRawDetailModel.objects.get(id=self.id)
                old_storeback_num=old_storeback_raw.storeback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name}不能供应{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            store_orders = self.storeback_order.supplier.storerawordermodel_set.all()
            material_sells = []
            for order in store_orders:
                material_sells.extend(
                    order.storerawdetailmodel_set.filter(raw=self.raw.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name} 物料{self.raw.raw.name}没有对应的入库记录，无法退库！"})
            else:
                # 计算总入库量
                total_sell = sum(item.store_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = StoreBackRawDetailModel.objects.filter(
                    storeback_order__supplier=self.storeback_order.supplier,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.storeback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.storeback_num > total_sell:
                    raise ValidationError({
                                              "storeback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.storeback_num}，总入库：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.store_order.date for item in material_sells)
                if self.storeback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "storeback_num": f"退库日期不能早于物料{self.raw.raw.name}的入库日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StoreRawDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.storeback_num-old_storeback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"store_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_storeback_num = 0
            if self.id:  # 修改操作
                old_storeback_raw = StoreBackRawDetailModel.objects.select_for_update().get(id=self.id)
                old_storeback_num = old_storeback_raw.storeback_num

            if self.raw_id:
                storeRawDetail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num -self.storeback_num + old_storeback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.storeback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreRawDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class StoreAdditiveOrderModel(models.Model):
    '''物料入库'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='入库日期', default=timezone.now)
    TYPE_CHOICES = [
        ("1", "入库单"),
        ("0", "期初入库单"),
    ]
    type = models.CharField(max_length=50, verbose_name='类型', default='1', choices=TYPE_CHOICES)
    INVOICE_CHOICES = [
        ("1", "没有发票"),
        ("0", "有发票"),
    ]
    invoice = models.CharField(max_length=50, verbose_name='类型', default='1', choices=INVOICE_CHOICES)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂入库管理'
        verbose_name_plural = '添加剂入库管理'  # 自定义复数形式，与单数相同

    @property
    def total_amount(self):
        """计算入库单总金额（含税）"""
        # 使用 F() 表达式进行数据库层面的计算（支持字段乘法）
        return self.storeadditivedetailmodel_set.filter(parent__isnull=True).aggregate(
            total=Sum(
                ExpressionWrapper(
                    F('price') * F('store_num') * (1 + F('tax') * 0.01),  # 正确的字段计算方式
                    output_field=DecimalField()
                )
            )
        )['total'] or 0

    def __str__(self):

        return '|'.join([self.orderId, f"总含税金额：{self.total_amount:.2f}", self.supplier.name])
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期小于入库日期！"})

            if not self.id: #新增
                if self.supplier.state !='1':
                    raise ValidationError({"supplier": f"{self.supplier.name}供应商不能停用，无法保存！"})
                if not self.orderId:


                    id_date=datetime.now()
                    if self.date:
                       id_date=self.date
                    orderIdList=StoreAdditiveOrderModel.objects.filter(orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',flat=True).first()

                    if orderIdList:
                        try:
                            result=orderIdList[:7]+str(int(orderIdList[7:])+1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result=id_date.strftime('%Y%m%d')+'0001'
                    # print(raw)
                    self.orderId=result

            try:
                 if len(self.orderId) == 12:
                   int(self.orderId[7:])
                 else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                 raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

class StoreAdditiveOrderFileModel(models.Model):
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 先定义动态 upload_to 函数（关键）
    def dynamic_upload_path(instance, filename):
        """
        动态生成文件上传路径
        格式: uploads/files/report/[用户ID]/[应用标签]/[模型名称]/[文件名]
        """
        # # 获取模型元数据
        # code = instance.code  # 应用标签
        #
        # # 获取用户ID（确保模型有user字段关联）
        # user_id = instance.user.id

        # 拼接路径（将特殊字符替换为下划线，避免路径错误）
        return f"uploads/files/store/additive/{instance.pk}/{filename}"
    file = models.ImageField(upload_to=dynamic_upload_path, validators=[validate_file_size],
                                   verbose_name='附件')


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '附件'
        verbose_name_plural = '附件'  # 自定义复数形式，与单数相同




class StoreAdditiveDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        baseModels.AdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
        related_name = 'store_details'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    location = models.ForeignKey(
        baseModels.LocationAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    batch_number = models.CharField(max_length=50, verbose_name='批号', default=None, null=False,  # 数据库层面不允许为NULL
                               blank=False)
    # 将原来的standard字符字段改为外键关联到StandardModel
    unit = models.ForeignKey(
        baseModels.UnitModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='单位',
        # related_name = 'store_details_unit'  # 自定义反向关联名（推荐使用）
    )

    store_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)
    price = models.DecimalField(verbose_name='单价', max_digits=20, decimal_places=13, default=0)
    tax = models.DecimalField(verbose_name='税率%', max_digits=3, decimal_places=0, default=13)


    remaining_num=models.DecimalField(verbose_name='剩余数量', max_digits=10,decimal_places=2,default=0)
    parent = models.ForeignKey(
        'self',
        on_delete=models.PROTECT,
        null=True,  # 数据库层面不允许为NULL
        blank=True,
        default=None,
        verbose_name='父级',
    )
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂入库明细'
        verbose_name_plural = '添加剂入库明细'  # 自定义复数形式，与单数相同
        # 自定义权限：格式为("权限代码", "权限描述")
        permissions = [
            # ("can_approve_order", "能审核入库单"),  # 审核权限
            # ("can_export_order", "能导出入库单数据"),  # 导出权限
            ("view_subledger_storeadditivedetailmodel", "查看明细账"),  # 统计查看权限
        ]


    def __str__(self):
        result='<'+'>  <'.join([self.raw.name, self.batch_number, '剩'+str(self.remaining_num)+str(self.unit.unit) ])+'>'
        return result
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.store_order.supplier.raws.all()

            if not self.id: #新增
                if self.raw.state !='1':
                    raise ValidationError({"raw": f"{self.raw.name}物料不能停用，无法保存！"})
                if self.store_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.store_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                self.remaining_num=self.store_num
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            else:
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                old_store_num=StoreAdditiveDetailModel.objects.filter(id=self.id).values_list('store_num').first()

                self.remaining_num=self.store_num-old_store_num[0]+self.remaining_num
                if self.remaining_num < 0:
                    raise ValidationError({"store_num": f"剩余数量不能小于0，无法修改保存！改后剩余数{self.remaining_num}"})
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).exclude(
                        id=self.id if self.id else None).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            if self.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.store_order.supplier.name}不供应{self.raw.name}物料，无法保存！"})

        except Exception as e:
            raise ValidationError(e)

class TransferAdditiveOrderModel(models.Model):
    '''物料调拨'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='调拨日期', default=timezone.now)


    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂调拨管理'
        verbose_name_plural = '添加剂调拨管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId



    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 执行调拨操作
                for detail in self.transferadditivedetailmodel_set.all():
                    # 减少源货位库存
                    from_store = detail.from_store_detail
                    if from_store.remaining_num-detail.transfer_num < 0:
                        raise ValidationError(f"源货位库存不足: {from_store}")
                    # 检查目标货位类型，如果是货位，确保没有其他库存
                    if detail.to_location.type == '0':  # 货位
                        existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                            id=detail.to_store_detail.id if detail.to_store_detail else None
                        ).exists()
                        if existing:
                            raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")


            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = TransferAdditiveOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})

        except Exception as e:
            raise ValidationError(e)
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # # 检查生产品种状态，不允许删除已保存品种的出库单
        # if self.product.state == '1':
        #     raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.transferadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                # store_raw_detail.save()
                detail.delete()
                #删除对应的入库数据
                # detail.to_store_detail.delete()
            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            # details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.transferadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                    store_raw_detail.remaining_num += detail.transfer_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    # store_raw_detail.save()
                    # 要先删除明细
                    detail.delete()
                    # # 删除对应的入库数据
                    # detail.to_store_detail.delete()
                # 删除明细
                # details.delete()

            # 批量删除主单
            queryset.delete()
class TransferAdditiveDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    transfer_order = models.ForeignKey(
        TransferAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    from_store_detail = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨前添加剂',
        related_name = 'store_details_before'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    to_location = models.ForeignKey(
        baseModels.LocationAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details_transfer'  # 自定义反向关联名（推荐使用）
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    to_store_detail = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨后添加剂',
        related_name='store_details_after'  # 自定义反向关联名（推荐使用）
    )

    transfer_num = models.DecimalField(verbose_name='调拨数量', max_digits=10,decimal_places=2)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂调拨明细'
        verbose_name_plural = '添加剂调拨明细'  # 自定义复数形式，与单数相同




    def clean(self):
        # 验证源和目标不能相同
        if self.from_store_detail.location == self.to_location:
            raise ValidationError({"to_location":"源货位和目标货位不能相同"})

        # 验证调拨数量
        if not self.transfer_num or self.transfer_num <= 0:
            raise ValidationError("调拨数量必须大于0")

        # 验证源货位有足够库存
        if self.from_store_detail.remaining_num < self.transfer_num:
            raise ValidationError("调拨数量不能大于源货位剩余数量")

        # 验证目标货位类型限制
        if self.to_location.type == '0':  # 货位
            # 检查目标货位是否已有其他库存
            try:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=self.to_store_detail.id if self.to_store_detail else None
                ).exists()
            except:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exists()
            if existing:
                raise ValidationError("目标货位为库位，已有其他货物，不能再调入")

        # # 如果指定了目标库存记录，验证是否匹配
        # if self.to_store_detail and self.to_store_detail.location != self.to_location:
        #     raise ValidationError("目标库存记录与目标货位不匹配")
        #
        # if self.to_store_detail and self.to_store_detail.raw != self.from_store_detail.raw:
        #     raise ValidationError("目标库存记录与源库存记录的物料不同")

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        # self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_transfer_num = 0
            if self.pk:  # 修改操作
                old_transfer_raw = TransferAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_transfer_num = old_transfer_raw.transfer_num
            if self.from_store_detail_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.transfer_num + old_transfer_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()
            try:
                if self.to_store_detail :
                    # 更新现有库存记录
                    to_store = self.to_store_detail
                    to_store.remaining_num = to_store.remaining_num+self.transfer_num-old_transfer_num
                    to_store.save()

            except:
                # 创建新的库存记录
                to_store = StoreAdditiveDetailModel(
                    store_order=self.from_store_detail.store_order,  # 调拨产生的库存没有对应的入库单
                    raw=self.from_store_detail.raw,
                    location=self.to_location,
                    batch_number=self.from_store_detail.batch_number,
                    unit=self.from_store_detail.unit,
                    store_num=self.transfer_num,
                    remaining_num=self.transfer_num,
                    parent=self.from_store_detail
                )
                to_store.save()
                self.to_store_detail = to_store


            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.transfer_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()


            # 执行实际删除操作
            super().delete(*args, **kwargs)
            self.to_store_detail.delete()
    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            needdels=[]
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update     ().get(id=detail.from_store_detail_id)
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！{detail.to_store_detail}")
                store_raw_detail.save()
                needdels.append(detail.to_store_detail)


            # 批量删除记录
            queryset.delete()
            for needdel in needdels:
                needdel.delete()
class LossAdditiveOrderModel(models.Model):
    '''报损管理'''
    orderId = models.CharField(max_length=50, verbose_name='报损单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报损日期', default=timezone.now)

    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂报损管理'
        verbose_name_plural = '添加剂报损管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:


                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = LossAdditiveOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.lossadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.lossadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.loss_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class LossAdditiveDetailModel(models.Model):
    '''报损单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    loss_order = models.ForeignKey(
        LossAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报损单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'loss_details'  # 自定义反向关联名（推荐使用）
    )

    loss_num = models.DecimalField(verbose_name='报损数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂报损明细'
        verbose_name_plural = '添加剂报损明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws=self.loss_order.customer.raws.all()
            old_loss_num=0
            if not self.id: #新增

                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"loss_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_loss_raw=LossAdditiveDetailModel.objects.get(id=self.id)
                old_loss_num=old_loss_raw.loss_num



            if self.raw_id:
                storeRawDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.loss_num+old_loss_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_loss_num = 0
            if self.id:  # 修改操作
                old_loss_raw = LossAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.loss_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SurplusAdditiveOrderModel(models.Model):
    '''报溢管理'''
    orderId = models.CharField(max_length=50, verbose_name='报溢单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报溢日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂报溢管理'
        verbose_name_plural = '添加剂报溢管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SurplusAdditiveOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.surplusadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.surplusadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.surplus_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class SurplusAdditiveDetailModel(models.Model):
    '''报溢单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    surplus_order = models.ForeignKey(
        SurplusAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报溢单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
        related_name='surplus_details'  # 自定义反向关联名（推荐使用）
    )

    surplus_num = models.DecimalField(verbose_name='报溢数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂报溢明细'
        verbose_name_plural = '添加剂报溢明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.surplus_num or self.surplus_num < 0:
                raise ValidationError({"surplus_num": f"数量不能小于0，无法保存！"})
            old_surplus_num = 0
            if self.id:  # 修改
                old_surplus_raw = SurplusAdditiveDetailModel.objects.get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_surplus_num = 0
            if self.id:  # 修改操作
                old_surplus_raw = SurplusAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.surplus_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class SellAdditiveOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂销售管理'
        verbose_name_plural = '添加剂销售管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于销售日期！"})

            if not self.id:  # 新增
                if self.customer.state != '1':
                    raise ValidationError({"customer": f"{self.customer.name}客户不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SellAdditiveOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.selladditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.selladditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.sell_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellAdditiveDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sell_order = models.ForeignKey(
        SellAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'sell_details'  # 自定义反向关联名（推荐使用）
    )

    sell_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂销售明细'
        verbose_name_plural = '添加剂销售明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.sell_order.customer.raws.all()
            old_sell_num=0
            if not self.id: #新增

                if self.sell_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sell_order.customer.name}客户不能停用，无法保存！"})
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sell_raw=SellAdditiveDetailModel.objects.get(id=self.id)
                old_sell_num=old_sell_raw.sell_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sell_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            if self.raw_id:
                storeRawDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.sell_num+old_sell_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sell_num = 0
            if self.id:  # 修改操作
                old_sell_raw = SellAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.sell_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundAdditiveOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂出库管理'
        verbose_name_plural = '添加剂出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundAdditiveOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backadditiveordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundAdditiveDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'outbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂出库明细'
        verbose_name_plural = '添加剂出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']
    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundAdditiveDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeAdditiveDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeAdditiveDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundAdditiveDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backadditiveordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backadditivedetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class OtherOutboundAdditiveOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='出库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True)


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂其他出库'
        verbose_name_plural = '添加剂其他出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OtherOutboundAdditiveOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.otheroutboundadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:


                # 处理关联明细，恢复库存
                details = order.otheroutboundadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class OtherOutboundAdditiveDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OtherOutboundAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'otheroutbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)



    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂其他出库明细'
        verbose_name_plural = '添加剂其他出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})

            old_out_num=0
            if  self.id: #修改
                old_out_raw=OtherOutboundAdditiveDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''


        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OtherOutboundAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class BackAdditiveOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂退库管理'
        verbose_name_plural = '添加剂退库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundadditiveordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backadditivedetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundadditivedetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackAdditiveDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackAdditiveOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackAdditiveDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'back_details'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂退库明细'
        verbose_name_plural = '添加剂退库明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundadditiveordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundadditivedetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackAdditiveDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackAdditiveDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreAdditiveDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundAdditiveOfIntermediateProductOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂出库管理(中间品)'
        verbose_name_plural = '添加剂出库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundAdditiveOfIntermediateProductOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundadditiveofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backadditiveofintermediateproductordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundadditiveofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundAdditiveOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundAdditiveOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'outbound_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂出库明细(中间品)'
        verbose_name_plural = '添加剂出库明细(中间品)'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']
    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundAdditiveOfIntermediateProductDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeAdditiveDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeAdditiveDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundAdditiveOfIntermediateProductDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backadditiveofintermediateproductordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backadditiveofintermediateproductdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundAdditiveOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class BackAdditiveOfIntermediateProductOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂退库管理（中间品）'
        verbose_name_plural = '添加剂退库管理（中间品）'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundadditiveofintermediateproductordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backadditiveofintermediateproductdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundadditiveofintermediateproductdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackAdditiveOfIntermediateProductDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackAdditiveOfIntermediateProductOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backadditiveofintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backadditiveofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackAdditiveOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackAdditiveOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'back_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂退库明细(中间品)'
        verbose_name_plural = '添加剂退库明细(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundadditiveofintermediateproductordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundadditiveofintermediateproductdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackAdditiveOfIntermediateProductDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackAdditiveOfIntermediateProductDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreAdditiveDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackAdditiveOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class SellBackAdditiveOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂销售退货管理'
        verbose_name_plural = '添加剂销售退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                sell_orders = self.customer.selladditiveordermodel_set.all()
                if not sell_orders.exists():
                    raise ValidationError({"customer": f"该客户没有对应的销售记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.sellbackadditivedetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的销售记录
                            material_sells = []
                            for order in sell_orders:
                                material_sells.extend(
                                    order.selladditivedetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"customer": f"物料{detail.raw.raw.name}没有对应的销售记录，无法退库！"})

                            # 计算该物料的总销售数量
                            total_sell = sum(item.sell_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = SellBackAdditiveDetailModel.objects.filter(
                                sellback_order__customer=self.customer,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.sellback_num for item in existing_back)

                            # 验证退库数量
                            if detail.sellback_num <= 0:
                                raise ValidationError({"customer": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.sellback_num > total_sell:
                                raise ValidationError({
                                                          "customer": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总销售：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.sell_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "customer": f"退库日期不能早于物料{detail.raw.raw.name}的销售日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = SellBackAdditiveOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellbackadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellbackadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.sellback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellBackAdditiveDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sellback_order = models.ForeignKey(
        SellBackAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='添加剂',
       related_name = 'sellback_details'  # 自定义反向关联名（推荐使用）
    )

    sellback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂销售退货明细'
        verbose_name_plural = '添加剂销售退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.sellback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.sellback_order.customer.raws.all()
            old_sellback_num=0
            if not self.id: #新增

                if self.sellback_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sellback_order.customer.name}客户不能停用，无法保存！"})
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sellback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sellback_raw=SellBackRawDetailModel.objects.get(id=self.id)
                old_sellback_num=old_sellback_raw.sellback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            sell_orders = self.sellback_order.customer.selladditiveordermodel_set.all()
            material_sells = []
            for order in sell_orders:
                material_sells.extend(
                    order.selladditivedetailmodel_set.filter(raw=self.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name} 物料{self.raw.raw.name}没有对应的销售记录，无法退库！"})
            else:
                # 计算总销售量
                total_sell = sum(item.sell_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = SellBackAdditiveDetailModel.objects.filter(
                    sellback_order__customer=self.sellback_order.customer,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.sellback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.sellback_num > total_sell:
                    raise ValidationError({
                                              "sellback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.sellback_num}，总销售：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.sell_order.date for item in material_sells)
                if self.sellback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "sellback_num": f"退库日期不能早于物料{self.raw.raw.name}的销售日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.sellback_num-old_sellback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sellback_num = 0
            if self.id:  # 修改操作
                old_sellback_raw = SellBackAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.sellback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class StoreBackAdditiveOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='入库退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='入库退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierAdditiveModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '添加剂入库退货管理'
        verbose_name_plural = '添加剂入库退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择要退货的供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                store_orders = self.supplier.storeadditiveordermodel_set.all()
                if not store_orders.exists():
                    raise ValidationError({"supplier": f"该供应商没有对应的入库记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.storebackadditivedetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的入库记录
                            material_sells = []
                            for order in store_orders:
                                material_sells.extend(
                                    order.storeadditivedetailmodel_set.filter(raw=detail.raw.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"supplier": f"物料{detail.raw.raw.name}没有对应的入库记录，无法退库！"})

                            # 计算该物料的总入库数量
                            total_sell = sum(item.store_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = StoreBackAdditiveDetailModel.objects.filter(
                                storeback_order__supplier=self.supplier,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.storeback_num for item in existing_back)

                            # 验证退库数量
                            if detail.storeback_num <= 0:
                                raise ValidationError({"supplier": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.storeback_num > total_sell:
                                raise ValidationError({
                                                          "supplier": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总入库：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.store_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "supplier": f"退库日期不能早于物料{detail.raw.raw.name}的入库日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = StoreBackAdditiveOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.storebackadditivedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.storebackadditivedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.storeback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class StoreBackAdditiveDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    storeback_order = models.ForeignKey(
        StoreBackAdditiveOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreAdditiveDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='原料',
       related_name = 'store_back_details'  # 自定义反向关联名（推荐使用）
    )

    storeback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='添加剂入库退货明细'
        verbose_name_plural = '添加剂入库退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.storeback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.storeback_order.supplier.raws.all()
            old_storeback_num=0
            if not self.id: #新增

                if self.storeback_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.storeback_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})
                old_storeback_raw=StoreBackAdditiveDetailModel.objects.get(id=self.id)
                old_storeback_num=old_storeback_raw.storeback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name}不能供应{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            store_orders = self.storeback_order.supplier.storeadditiveordermodel_set.all()
            material_sells = []
            for order in store_orders:
                material_sells.extend(
                    order.storeadditivedetailmodel_set.filter(raw=self.raw.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name} 物料{self.raw.raw.name}没有对应的入库记录，无法退库！"})
            else:
                # 计算总入库量
                total_sell = sum(item.store_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = StoreBackAdditiveDetailModel.objects.filter(
                    storeback_order__supplier=self.storeback_order.supplier,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.storeback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.storeback_num > total_sell:
                    raise ValidationError({
                                              "storeback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.storeback_num}，总入库：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.store_order.date for item in material_sells)
                if self.storeback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "storeback_num": f"退库日期不能早于物料{self.raw.raw.name}的入库日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StoreAdditiveDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.storeback_num-old_storeback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"store_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_storeback_num = 0
            if self.id:  # 修改操作
                old_storeback_raw = StoreBackAdditiveDetailModel.objects.select_for_update().get(id=self.id)
                old_storeback_num = old_storeback_raw.storeback_num

            if self.raw_id:
                storeRawDetail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num -self.storeback_num + old_storeback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.storeback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreAdditiveDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()



class StorePackagingOrderModel(models.Model):
    '''物料入库'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='入库日期', default=timezone.now)
    TYPE_CHOICES = [
        ("1", "入库单"),
        ("0", "期初入库单"),
    ]
    type = models.CharField(max_length=50, verbose_name='类型', default='1', choices=TYPE_CHOICES)
    INVOICE_CHOICES = [
        ("1", "没有发票"),
        ("0", "有发票"),
    ]
    invoice = models.CharField(max_length=50, verbose_name='发票', default='1', choices=INVOICE_CHOICES)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )

    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材入库管理'
        verbose_name_plural = '包材入库管理'  # 自定义复数形式，与单数相同

    @property
    def total_amount(self):
        """计算入库单总金额（含税）"""
        # 使用 F() 表达式进行数据库层面的计算（支持字段乘法）
        return self.storepackagingdetailmodel_set.filter(parent__isnull=True).aggregate(
            total=Sum(
                ExpressionWrapper(
                    F('price') * F('store_num') * (1 + F('tax')*0.01),  # 正确的字段计算方式
                    output_field=DecimalField()
                )
            )
        )['total'] or 0
    def __str__(self):

        return '|'.join([self.orderId,f"总含税金额：{self.total_amount:.2f}",self.supplier.name])
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期小于入库日期！"})

            if not self.id: #新增
                if self.supplier.state !='1':
                    raise ValidationError({"supplier": f"{self.supplier.name}供应商不能停用，无法保存！"})
                if not self.orderId:


                    id_date=datetime.now()
                    if self.date:
                       id_date=self.date
                    orderIdList=StorePackagingOrderModel.objects.filter(orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',flat=True).first()

                    if orderIdList:
                        try:
                            result=orderIdList[:7]+str(int(orderIdList[7:])+1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result=id_date.strftime('%Y%m%d')+'0001'
                    # print(raw)
                    self.orderId=result

            try:
                 if len(self.orderId) == 12:
                   int(self.orderId[7:])
                 else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                 raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法：若已被发票关联，则阻止删除"""
        # 检查当前入库单是否被任何发票关联（通过多对多反向查询）
        if self.invoicepackagingmodel_set.exists():
            # 获取关联的发票号，便于提示
            related_invoices = self.invoicepackagingmodel_set.values_list('invoice_num', flat=True)
            raise ValidationError(
                f"无法删除入库单 {self.orderId}！该入库单已被以下发票关联：{', '.join(related_invoices)}"
            )
        # 未被关联，正常删除
        super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查当前入库单是否被发票关联
                if order.invoicepackagingmodel_set.exists():
                    # 获取关联的发票号，便于提示
                    related_invoices = order.invoicepackagingmodel_set.values_list('invoice_num', flat=True)

                    raise ValidationError(
                        f"无法删除入库单 {order.orderId}！该入库单已被以下发票关联：{', '.join(related_invoices)}"
                    )
                else:
                    # 批量删除主单
                    order.delete()
class StorePackagingOrderFileModel(models.Model):
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StorePackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 先定义动态 upload_to 函数（关键）
    def dynamic_upload_path(instance, filename):
        """
        动态生成文件上传路径
        格式: uploads/files/report/[用户ID]/[应用标签]/[模型名称]/[文件名]
        """
        # # 获取模型元数据
        # code = instance.code  # 应用标签
        #
        # # 获取用户ID（确保模型有user字段关联）
        # user_id = instance.user.id

        # 拼接路径（将特殊字符替换为下划线，避免路径错误）
        return f"uploads/files/store/packaging/{instance.pk}/{filename}"
    file = models.ImageField(upload_to=dynamic_upload_path, validators=[validate_file_size],
                                   verbose_name='附件')


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '附件'
        verbose_name_plural = '附件'  # 自定义复数形式，与单数相同




class StorePackagingDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StorePackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        baseModels.PackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
        related_name = 'store_details'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    location = models.ForeignKey(
        baseModels.LocationPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    batch_number = models.CharField(max_length=50, verbose_name='批号', default=None, null=False,  # 数据库层面不允许为NULL
                               blank=False)
    # 将原来的standard字符字段改为外键关联到StandardModel
    unit = models.ForeignKey(
        baseModels.UnitModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='单位',
        # related_name = 'store_details_unit'  # 自定义反向关联名（推荐使用）
    )

    store_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)
    price = models.DecimalField(verbose_name='单价', max_digits=20, decimal_places=13, default=0)
    tax = models.DecimalField(verbose_name='税率%', max_digits=3, decimal_places=0, default=13)
    remaining_num=models.DecimalField(verbose_name='剩余数量', max_digits=10,decimal_places=2,default=0)
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    parent = models.ForeignKey(
        'self',
        on_delete=models.PROTECT,
        null=True,  # 数据库层面不允许为NULL
        blank=True,
        default=None,
        verbose_name='父级',
    )
    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材入库明细'
        verbose_name_plural = '包材入库明细'  # 自定义复数形式，与单数相同
        # 自定义权限：格式为("权限代码", "权限描述")
        permissions = [
            # ("can_approve_order", "能审核入库单"),  # 审核权限
            # ("can_export_order", "能导出入库单数据"),  # 导出权限
            ("view_subledger_storepackagingdetailmodel", "查看明细账"),  # 统计查看权限
        ]


    def __str__(self):
        result='<'+'>  <'.join([self.raw.name, self.batch_number, '剩'+str(self.remaining_num)+str(self.unit.unit) ])+'>'
        return result
    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.store_order.supplier.raws.all()

            if not self.id: #新增
                if self.raw.state !='1':
                    raise ValidationError({"raw": f"{self.raw.name}物料不能停用，无法保存！"})
                if self.store_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.store_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                self.remaining_num=self.store_num
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            else:
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                old_store_num=StorePackagingDetailModel.objects.filter(id=self.id).values_list('store_num').first()

                self.remaining_num=self.store_num-old_store_num[0]+self.remaining_num
                if self.remaining_num < 0:
                    raise ValidationError({"store_num": f"剩余数量不能小于0，无法修改保存！改后剩余数{self.remaining_num}"})
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).exclude(
                        id=self.id if self.id else None).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            if self.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.store_order.supplier.name}不供应{self.raw.name}物料，无法保存！"})

        except Exception as e:
            raise ValidationError(e)

            # raise  ValidationError({"raw": f"{e}错误！"})
class TransferPackagingOrderModel(models.Model):
    '''物料调拨'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='调拨日期', default=timezone.now)


    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材调拨管理'
        verbose_name_plural = '包材调拨管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def complete(self):
        """完成调拨单"""
        # 执行调拨操作
        for detail in self.transferpackagingdetailmodel_set.all():
            # 减少源货位库存
            from_store = detail.from_store_detail
            from_store.remaining_num -= detail.transfer_num
            if from_store.remaining_num < 0:
                raise ValidationError(f"源货位库存不足: {from_store}")
            from_store.save()

            # 增加目标货位库存
            # 检查目标货位类型，如果是货位，确保没有其他库存
            if detail.to_location.type == '0':  # 货位
                existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=detail.to_store_detail.id if detail.to_store_detail else None
                ).exists()
                if existing:
                    raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")

            if detail.to_store_detail:
                # 更新现有库存记录
                to_store = detail.to_store_detail
                to_store.remaining_num += detail.transfer_num
                to_store.save()
            else:
                # 创建新的库存记录
                to_store = StorePackagingDetailModel(
                    store_order=None,  # 调拨产生的库存没有对应的入库单
                    raw=detail.from_store_detail.raw,
                    location=detail.to_location,
                    batch_number=detail.from_store_detail.batch_number,
                    unit=detail.from_store_detail.unit,
                    store_num=detail.transfer_num,
                    remaining_num=detail.transfer_num
                )
                to_store.save()
                detail.to_store_detail = to_store
                detail.save()


        self.save()

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 执行调拨操作
                for detail in self.transferpackagingdetailmodel_set.all():
                    # 减少源货位库存
                    from_store = detail.from_store_detail
                    if from_store.remaining_num-detail.transfer_num < 0:
                        raise ValidationError(f"源货位库存不足: {from_store}")
                    # 检查目标货位类型，如果是货位，确保没有其他库存
                    if detail.to_location.type == '0':  # 货位
                        existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                            id=detail.to_store_detail.id if detail.to_store_detail else None
                        ).exists()
                        if existing:
                            raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")


            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = TransferPackagingOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})

        except Exception as e:
            raise ValidationError(e)
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # # 检查生产品种状态，不允许删除已保存品种的出库单
        # if self.product.state == '1':
        #     raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.transferpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                # store_raw_detail.save()
                detail.delete()
                #删除对应的入库数据
                # detail.to_store_detail.delete()
            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            # details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.transferpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                    store_raw_detail.remaining_num += detail.transfer_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    # store_raw_detail.save()
                    # 要先删除明细
                    detail.delete()
                    # # 删除对应的入库数据
                    # detail.to_store_detail.delete()
                # 删除明细
                # details.delete()

            # 批量删除主单
            queryset.delete()
class TransferPackagingDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    transfer_order = models.ForeignKey(
        TransferPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    from_store_detail = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨前包材',
        related_name = 'store_details_before'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    to_location = models.ForeignKey(
        baseModels.LocationPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details_transfer'  # 自定义反向关联名（推荐使用）
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    to_store_detail = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨后包材',
        related_name='store_details_after'  # 自定义反向关联名（推荐使用）
    )

    transfer_num = models.DecimalField(verbose_name='调拨数量', max_digits=10,decimal_places=2)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材调拨明细'
        verbose_name_plural = '包材调拨明细'  # 自定义复数形式，与单数相同




    def clean(self):
        # 验证源和目标不能相同
        if self.from_store_detail.location == self.to_location:
            raise ValidationError({"to_location":"源货位和目标货位不能相同"})

        # 验证调拨数量
        if not self.transfer_num or self.transfer_num <= 0:
            raise ValidationError("调拨数量必须大于0")

        # 验证源货位有足够库存
        if self.from_store_detail.remaining_num < self.transfer_num:
            raise ValidationError("调拨数量不能大于源货位剩余数量")

        # 验证目标货位类型限制
        if self.to_location.type == '0':  # 货位
            # 检查目标货位是否已有其他库存
            try:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=self.to_store_detail.id if self.to_store_detail else None
                ).exists()
            except:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exists()
            if existing:
                raise ValidationError("目标货位为库位，已有其他货物，不能再调入")

        # # 如果指定了目标库存记录，验证是否匹配
        # if self.to_store_detail and self.to_store_detail.location != self.to_location:
        #     raise ValidationError("目标库存记录与目标货位不匹配")
        #
        # if self.to_store_detail and self.to_store_detail.raw != self.from_store_detail.raw:
        #     raise ValidationError("目标库存记录与源库存记录的物料不同")

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        # self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_transfer_num = 0
            if self.pk:  # 修改操作
                old_transfer_raw = TransferPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_transfer_num = old_transfer_raw.transfer_num
            if self.from_store_detail_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.transfer_num + old_transfer_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()
            try:
                if self.to_store_detail :
                    # 更新现有库存记录
                    to_store = self.to_store_detail
                    to_store.remaining_num = to_store.remaining_num+self.transfer_num-old_transfer_num
                    to_store.save()

            except:
                # 创建新的库存记录
                to_store = StorePackagingDetailModel(
                    store_order=self.from_store_detail.store_order,  # 调拨产生的库存没有对应的入库单
                    raw=self.from_store_detail.raw,
                    location=self.to_location,
                    batch_number=self.from_store_detail.batch_number,
                    unit=self.from_store_detail.unit,
                    store_num=self.transfer_num,
                    remaining_num=self.transfer_num,
                    parent=self.from_store_detail
                )
                to_store.save()
                self.to_store_detail = to_store


            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.transfer_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()


            # 执行实际删除操作
            super().delete(*args, **kwargs)
            self.to_store_detail.delete()
    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            needdels=[]
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update     ().get(id=detail.from_store_detail_id)
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！{detail.to_store_detail}")
                store_raw_detail.save()
                needdels.append(detail.to_store_detail)


            # 批量删除记录
            queryset.delete()
            for needdel in needdels:
                needdel.delete()
class LossPackagingOrderModel(models.Model):
    '''报损管理'''
    orderId = models.CharField(max_length=50, verbose_name='报损单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报损日期', default=timezone.now)

    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材报损管理'
        verbose_name_plural = '包材报损管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:


                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = LossPackagingOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.losspackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.losspackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.loss_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class LossPackagingDetailModel(models.Model):
    '''报损单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    loss_order = models.ForeignKey(
        LossPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报损单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'loss_details'  # 自定义反向关联名（推荐使用）
    )

    loss_num = models.DecimalField(verbose_name='报损数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材报损明细'
        verbose_name_plural = '包材报损明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws=self.loss_order.customer.raws.all()
            old_loss_num=0
            if not self.id: #新增

                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"loss_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_loss_raw=LossPackagingDetailModel.objects.get(id=self.id)
                old_loss_num=old_loss_raw.loss_num



            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.loss_num+old_loss_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_loss_num = 0
            if self.id:  # 修改操作
                old_loss_raw = LossPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.loss_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SurplusPackagingOrderModel(models.Model):
    '''报溢管理'''
    orderId = models.CharField(max_length=50, verbose_name='报溢单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报溢日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材报溢管理'
        verbose_name_plural = '包材报溢管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SurplusPackagingOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.surpluspackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.surpluspackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.surplus_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class SurplusPackagingDetailModel(models.Model):
    '''报溢单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    surplus_order = models.ForeignKey(
        SurplusPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报溢单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
        related_name='surplus_details'  # 自定义反向关联名（推荐使用）
    )

    surplus_num = models.DecimalField(verbose_name='报溢数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材报溢明细'
        verbose_name_plural = '包材报溢明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.surplus_num or self.surplus_num < 0:
                raise ValidationError({"surplus_num": f"数量不能小于0，无法保存！"})
            old_surplus_num = 0
            if self.id:  # 修改
                old_surplus_raw = SurpluspackagingDetailModel.objects.get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_surplus_num = 0
            if self.id:  # 修改操作
                old_surplus_raw = SurplusPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.surplus_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class SellPackagingOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材销售管理'
        verbose_name_plural = '包材销售管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于销售日期！"})

            if not self.id:  # 新增
                if self.customer.state != '1':
                    raise ValidationError({"customer": f"{self.customer.name}客户不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SellPackagingOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.sell_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellPackagingDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sell_order = models.ForeignKey(
        SellPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'sell_details'  # 自定义反向关联名（推荐使用）
    )

    sell_num = models.DecimalField(verbose_name='数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材销售明细'
        verbose_name_plural = '包材销售明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws=self.sell_order.customer.raws.all()
            old_sell_num=0
            if not self.id: #新增

                if self.sell_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sell_order.customer.name}客户不能停用，无法保存！"})
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sell_raw=SellPackagingDetailModel.objects.get(id=self.id)
                old_sell_num=old_sell_raw.sell_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sell_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.sell_num+old_sell_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sell_num = 0
            if self.id:  # 修改操作
                old_sell_raw = SellPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.sell_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundPackagingOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材出库管理'
        verbose_name_plural = '包材出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundPackagingOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backpackagingordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundPackagingDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'outbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材出库明细'
        verbose_name_plural = '包材出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']
    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundPackagingDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundPackagingDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backpackagingordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backpackagingdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class OtherOutboundPackagingOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='出库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True)


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材其他出库'
        verbose_name_plural = '包材其他出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OtherOutboundPackagingOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.otheroutboundpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:


                # 处理关联明细，恢复库存
                details = order.otheroutboundpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class OtherOutboundPackagingDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OtherOutboundPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'otheroutbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)



    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材其他出库明细'
        verbose_name_plural = '包材其他出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})

            old_out_num=0
            if  self.id: #修改
                old_out_raw=OtherOutboundPackagingDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''


        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OtherOutboundPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class BackPackagingOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材退库管理'
        verbose_name_plural = '包材退库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundpackagingordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backpackagingdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundpackagingdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackPackagingDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackPackagingOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackPackagingDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'back_details'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材退库明细'
        verbose_name_plural = '包材退库明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundpackagingordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundpackagingdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackPackagingDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackPackagingDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StorePackagingDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class OutboundPackagingOfIntermediateProductOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材出库管理(中间品)'
        verbose_name_plural = '包材出库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundPackagingOfIntermediateProductOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundpackagingofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backpackagingofintermediateproductordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundpackagingofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundPackagingOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundPackagingOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'outbound_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材出库明细(中间品)'
        verbose_name_plural = '包材出库明细(中间品)'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']
    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num=0
            if  self.id: #修改
                old_out_raw=OutboundPackagingOfIntermediateProductDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundPackagingOfIntermediateProductDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backpackagingofintermediateproductordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backpackagingofintermediateproductdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                                              "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {"outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundPackagingOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class BackPackagingOfIntermediateProductOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材退库管理(中间品)'
        verbose_name_plural = '包材退库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            #新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:   # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundpackagingofintermediateproductordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backpackagingofintermediateproductdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists() :  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundpackagingofintermediateproductdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})


                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackPackagingOfIntermediateProductDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:

                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:

                                raise ValidationError({"product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId: #新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackPackagingOfIntermediateProductOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':

                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backpackagingofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backpackagingofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackPackagingOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackPackagingOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'back_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材退库明细(中间品)'
        verbose_name_plural = '包材退库明细(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""


        # 验证主订单是否存在（有主键）
        if not self.back_order :

            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundpackagingofintermediateproductordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundpackagingofintermediateproductdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackPackagingOfIntermediateProductDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:

                    raise ValidationError({"back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:

                    raise ValidationError({"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackPackagingOfIntermediateProductDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StorePackagingDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:

                    raise ValidationError(
                        {"back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackPackagingOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num<0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SellBackPackagingOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材销售退货管理'
        verbose_name_plural = '包材销售退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                sell_orders = self.customer.sellpackagingordermodel_set.all()
                if not sell_orders.exists():
                    raise ValidationError({"customer": f"该客户没有对应的销售记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.sellbackpackagingdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的销售记录
                            material_sells = []
                            for order in sell_orders:
                                material_sells.extend(
                                    order.sellpackagingdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"customer": f"物料{detail.raw.raw.name}没有对应的销售记录，无法退库！"})

                            # 计算该物料的总销售数量
                            total_sell = sum(item.sell_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = SellBackPackagingDetailModel.objects.filter(
                                sellback_order__customer=self.customer,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.sellback_num for item in existing_back)

                            # 验证退库数量
                            if detail.sellback_num <= 0:
                                raise ValidationError({"customer": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.sellback_num > total_sell:
                                raise ValidationError({
                                                          "customer": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总销售：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.sell_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "customer": f"退库日期不能早于物料{detail.raw.raw.name}的销售日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = SellBackPackagingOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellbackpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellbackpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.sellback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class SellBackPackagingDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sellback_order = models.ForeignKey(
        SellBackPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'sellback_details'  # 自定义反向关联名（推荐使用）
    )

    sellback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材销售退货明细'
        verbose_name_plural = '包材销售退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.sellback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.sellback_order.customer.raws.all()
            old_sellback_num=0
            if not self.id: #新增

                if self.sellback_order.customer.state !='1':
                    raise ValidationError({"raw": f"{self.sellback_order.customer.name}客户不能停用，无法保存！"})
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sellback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sellback_raw=SellBackPackagingDetailModel.objects.get(id=self.id)
                old_sellback_num=old_sellback_raw.sellback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            sell_orders = self.sellback_order.customer.sellpackagingordermodel_set.all()
            material_sells = []
            for order in sell_orders:
                material_sells.extend(
                    order.sellpackagingdetailmodel_set.filter(raw=self.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"客户{self.sellback_order.customer.name} 物料{self.raw.raw.name}没有对应的销售记录，无法退库！"})
            else:
                # 计算总销售量
                total_sell = sum(item.sell_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = SellBackPackagingDetailModel.objects.filter(
                    sellback_order__customer=self.sellback_order.customer,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.sellback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.sellback_num > total_sell:
                    raise ValidationError({
                                              "sellback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.sellback_num}，总销售：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.sell_order.date for item in material_sells)
                if self.sellback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "sellback_num": f"退库日期不能早于物料{self.raw.raw.name}的销售日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.sellback_num-old_sellback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"sell_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sellback_num = 0
            if self.id:  # 修改操作
                old_sellback_raw = SellBackPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.sellback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StorePackagingwDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class StoreBackPackagingOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='入库退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='入库退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    supplier = models.ForeignKey(
        baseModels.SupplierPackagingModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='供应商',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '包材入库退货管理'
        verbose_name_plural = '包材入库退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择要退货的供应商！"})
            if self.supplier.exp_date < self.date:
                raise ValidationError({"supplier": f"供应商有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                store_orders = self.supplier.storepackagingordermodel_set.all()
                if not store_orders.exists():
                    raise ValidationError({"supplier": f"该供应商没有对应的入库记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.storebackpackagingdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的入库记录
                            material_sells = []
                            for order in store_orders:
                                material_sells.extend(
                                    order.storepackagingdetailmodel_set.filter(raw=detail.raw.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"supplier": f"物料{detail.raw.raw.name}没有对应的入库记录，无法退库！"})

                            # 计算该物料的总入库数量
                            total_sell = sum(item.store_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = StoreBackPackagingwDetailModel.objects.filter(
                                storeback_order__supplier=self.supplier,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.storeback_num for item in existing_back)

                            # 验证退库数量
                            if detail.storeback_num <= 0:
                                raise ValidationError({"supplier": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.storeback_num > total_sell:
                                raise ValidationError({
                                                          "supplier": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总入库：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.store_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                                          "supplier": f"退库日期不能早于物料{detail.raw.raw.name}的入库日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = StoreBackPackagingOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.storebackpackagingdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.storebackpackagingdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.storeback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()

class StoreBackPackagingDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    storeback_order = models.ForeignKey(
        StoreBackPackagingOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StorePackagingDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='包材',
       related_name = 'store_back_details'  # 自定义反向关联名（推荐使用）
    )

    storeback_num = models.DecimalField(verbose_name='退货数量', max_digits=10,decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='包材入库退货明细'
        verbose_name_plural = '包材入库退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.storeback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws=self.storeback_order.supplier.raws.all()
            old_storeback_num=0
            if not self.id: #新增

                if self.storeback_order.supplier.state !='1':
                    raise ValidationError({"raw": f"{self.storeback_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.storeback_num or self.storeback_num < 0:
                    raise ValidationError({"storeback_num": f"数量不能小于0，无法保存！"})
                old_storeback_raw=StoreBackPackagingDetailModel.objects.get(id=self.id)
                old_storeback_num=old_storeback_raw.storeback_num



            if self.raw.raw not in raws:
                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name}不能供应{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            store_orders = self.storeback_order.supplier.storepackagingordermodel_set.all()
            material_sells = []
            for order in store_orders:
                material_sells.extend(
                    order.storepackagingdetailmodel_set.filter(raw=self.raw.raw)
                )

            if not material_sells:

                raise ValidationError({"raw": f"供应商{self.storeback_order.supplier.name} 物料{self.raw.raw.name}没有对应的入库记录，无法退库！"})
            else:
                # 计算总入库量
                total_sell = sum(item.store_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = StoreBackPackagingDetailModel.objects.filter(
                    storeback_order__supplier=self.storeback_order.supplier,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.storeback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.storeback_num > total_sell:
                    raise ValidationError({
                                              "storeback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.storeback_num}，总入库：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.store_order.date for item in material_sells)
                if self.storeback_order.date < earliest_sell_date:
                    raise ValidationError({
                                              "storeback_num": f"退库日期不能早于物料{self.raw.raw.name}的入库日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail=StorePackagingDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num+self.storeback_num-old_storeback_num
                if remaining_num<0:
                    raise ValidationError(
                        {"store_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_storeback_num = 0
            if self.id:  # 修改操作
                old_storeback_raw = StoreBackPackagingDetailModel.objects.select_for_update().get(id=self.id)
                old_storeback_num = old_storeback_raw.storeback_num

            if self.raw_id:
                storeRawDetail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num -self.storeback_num + old_storeback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.storeback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StorePackagingDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.storeback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()



class StoreIntermediateOrderModel(models.Model):
    '''物料入库'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='入库日期', default=timezone.now)
    TYPE_CHOICES = [
        ("1", "入库单"),
        ("0", "期初入库单"),
    ]
    type = models.CharField(max_length=50, verbose_name='类型', default='1', choices=TYPE_CHOICES)

    # 将原来的standard字符字段改为外键关联到StandardModel
    # supplier = models.ForeignKey(
    #     baseModels.SupplierRawModel,
    #     on_delete=models.PROTECT,
    #     null=False,  # 数据库层面不允许为NULL
    #     blank=False,
    #     verbose_name='供应商',
    # )
    SUPPLIER_CHOICES = [
        ("1", "植物饮料"),
        ("0", "蛋白饮料"),
        ("2", "复配食品添加剂"),
    ]
    supplier= models.CharField(max_length=50, verbose_name='类型', default='1', choices=SUPPLIER_CHOICES)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品入库管理'
        verbose_name_plural = '中间品入库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.supplier:
                raise ValidationError({"supplier": f"没有选择供应商！"})
            # if self.supplier.exp_date < self.date:
            #     raise ValidationError({"supplier": f"供应商有效期小于入库日期！"})

            if not self.id:  # 新增
                # if self.supplier.state != '1':
                #     raise ValidationError({"supplier": f"{self.supplier.name}供应商不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = StoreIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)


class StoreIntermediateOrderFileModel(models.Model):
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 先定义动态 upload_to 函数（关键）
    def dynamic_upload_path(instance, filename):
        """
        动态生成文件上传路径
        格式: uploads/files/report/[用户ID]/[应用标签]/[模型名称]/[文件名]
        """
        # # 获取模型元数据
        # code = instance.code  # 应用标签
        #
        # # 获取用户ID（确保模型有user字段关联）
        # user_id = instance.user.id

        # 拼接路径（将特殊字符替换为下划线，避免路径错误）
        return f"uploads/files/store/intermediate/{instance.pk}/{filename}"

    file = models.ImageField(upload_to=dynamic_upload_path, validators=[validate_file_size],
                             verbose_name='附件')

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '附件'
        verbose_name_plural = '附件'  # 自定义复数形式，与单数相同


class StoreIntermediateDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        baseModels.IntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    location = models.ForeignKey(
        baseModels.LocationIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    batch_number = models.CharField(max_length=50, verbose_name='批号', default=None, null=False,  # 数据库层面不允许为NULL
                                    blank=False)
    # 将原来的standard字符字段改为外键关联到StandardModel
    unit = models.ForeignKey(
        baseModels.UnitModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='单位',
        # related_name='store_details_unit'  # 自定义反向关联名（推荐使用）
    )

    store_num = models.DecimalField(verbose_name='数量', max_digits=10, decimal_places=2)
    price = models.DecimalField(verbose_name='单价', max_digits=20, decimal_places=13, default=0)
    tax = models.DecimalField(verbose_name='税率%', max_digits=3, decimal_places=0, default=13)
    remaining_num = models.DecimalField(verbose_name='剩余数量', max_digits=10, decimal_places=2, default=0)
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    parent = models.ForeignKey(
        'self',
        on_delete=models.PROTECT,
        null=True,  # 数据库层面不允许为NULL
        blank=True,
        default=None,
        verbose_name='父级',
    )
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品入库明细'
        verbose_name_plural = '中间品入库明细'  # 自定义复数形式，与单数相同
        # 自定义权限：格式为("权限代码", "权限描述")
        permissions = [
            # ("can_approve_order", "能审核入库单"),  # 审核权限
            # ("can_export_order", "能导出入库单数据"),  # 导出权限
            ("view_subledger_storeintermediatedetailmodel", "查看明细账"),  # 统计查看权限
        ]
    def __str__(self):
        result = '<' + '>  <'.join(
            [self.raw.name, self.batch_number, '剩' + str(self.remaining_num) + str(self.unit.unit)]) + '>'
        return result

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws = self.store_order.supplier.raws.all()

            if not self.id:  # 新增
                # if self.raw.state != '1':
                #     raise ValidationError({"raw": f"{self.raw.name}物料不能停用，无法保存！"})
                # if self.store_order.supplier.state != '1':
                #     raise ValidationError({"raw": f"{self.store_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                self.remaining_num = self.store_num
                #验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            else:
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                old_store_num = StoreIntermediateDetailModel.objects.filter(id=self.id).values_list('store_num').first()

                self.remaining_num = self.store_num - old_store_num[0] + self.remaining_num
                if self.remaining_num < 0:
                    raise ValidationError(
                        {"store_num": f"剩余数量不能小于0，无法修改保存！改后剩余数{self.remaining_num}"})
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).exclude(
                        id=self.id if self.id else None).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            # if self.raw not in raws:
            #     raise ValidationError(
            #         {"raw": f"供应商{self.store_order.supplier.name}不供应{self.raw.name}物料，无法保存！"})

        except Exception as e:
            raise ValidationError(e)

            # raise  ValidationError({"raw": f"{e}错误！"})

class TransferIntermediateOrderModel(models.Model):
    '''物料调拨'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='调拨日期', default=timezone.now)


    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='中间品调拨管理'
        verbose_name_plural = '中间品调拨管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId



    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 执行调拨操作
                for detail in self.transferintermediatedetailmodel_set.all():
                    # 减少源货位库存
                    from_store = detail.from_store_detail
                    if from_store.remaining_num-detail.transfer_num < 0:
                        raise ValidationError(f"源货位库存不足: {from_store}")
                    # 检查目标货位类型，如果是货位，确保没有其他库存
                    if detail.to_location.type == '0':  # 货位
                        existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                            id=detail.to_store_detail.id if detail.to_store_detail else None
                        ).exists()
                        if existing:
                            raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")


            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = TransferIntermediateOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})

        except Exception as e:
            raise ValidationError(e)
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # # 检查生产品种状态，不允许删除已保存品种的出库单
        # if self.product.state == '1':
        #     raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.transferintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                # store_raw_detail.save()
                detail.delete()
                #删除对应的入库数据
                # detail.to_store_detail.delete()
            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            # details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.transferintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                    store_raw_detail.remaining_num += detail.transfer_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    # store_raw_detail.save()
                    # 要先删除明细
                    detail.delete()
                    # # 删除对应的入库数据
                    # detail.to_store_detail.delete()
                # 删除明细
                # details.delete()

            # 批量删除主单
            queryset.delete()
class TransferIntermediateDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    transfer_order = models.ForeignKey(
        TransferIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    from_store_detail = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨前中间品',
        related_name = 'store_details_before'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    to_location = models.ForeignKey(
        baseModels.LocationIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details_transfer'  # 自定义反向关联名（推荐使用）
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    to_store_detail = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨后中间品',
        related_name='store_details_after'  # 自定义反向关联名（推荐使用）
    )

    transfer_num = models.DecimalField(verbose_name='调拨数量', max_digits=10,decimal_places=2)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='中间品调拨明细'
        verbose_name_plural = '中间品调拨明细'  # 自定义复数形式，与单数相同




    def clean(self):
        # 验证源和目标不能相同
        if self.from_store_detail.location == self.to_location:
            raise ValidationError({"to_location":"源货位和目标货位不能相同"})

        # 验证调拨数量
        if not self.transfer_num or self.transfer_num <= 0:
            raise ValidationError("调拨数量必须大于0")

        # 验证源货位有足够库存
        if self.from_store_detail.remaining_num < self.transfer_num:
            raise ValidationError("调拨数量不能大于源货位剩余数量")

        # 验证目标货位类型限制
        if self.to_location.type == '0':  # 货位
            # 检查目标货位是否已有其他库存
            try:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=self.to_store_detail.id if self.to_store_detail else None
                ).exists()
            except:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exists()
            if existing:
                raise ValidationError("目标货位为库位，已有其他货物，不能再调入")

        # # 如果指定了目标库存记录，验证是否匹配
        # if self.to_store_detail and self.to_store_detail.location != self.to_location:
        #     raise ValidationError("目标库存记录与目标货位不匹配")
        #
        # if self.to_store_detail and self.to_store_detail.raw != self.from_store_detail.raw:
        #     raise ValidationError("目标库存记录与源库存记录的物料不同")

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        # self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_transfer_num = 0
            if self.pk:  # 修改操作
                old_transfer_raw = TransferIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_transfer_num = old_transfer_raw.transfer_num
            if self.from_store_detail_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.transfer_num + old_transfer_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()
            try:
                if self.to_store_detail :
                    # 更新现有库存记录
                    to_store = self.to_store_detail
                    to_store.remaining_num = to_store.remaining_num+self.transfer_num-old_transfer_num
                    to_store.save()

            except:
                # 创建新的库存记录
                to_store = StoreIntermediateDetailModel(
                    store_order=self.from_store_detail.store_order,  # 调拨产生的库存没有对应的入库单
                    raw=self.from_store_detail.raw,
                    location=self.to_location,
                    batch_number=self.from_store_detail.batch_number,
                    unit=self.from_store_detail.unit,
                    store_num=self.transfer_num,
                    remaining_num=self.transfer_num,
                    parent=self.from_store_detail
                )
                to_store.save()
                self.to_store_detail = to_store


            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.transfer_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()


            # 执行实际删除操作
            super().delete(*args, **kwargs)
            self.to_store_detail.delete()
    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            needdels=[]
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update     ().get(id=detail.from_store_detail_id)
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！{detail.to_store_detail}")
                store_raw_detail.save()
                needdels.append(detail.to_store_detail)


            # 批量删除记录
            queryset.delete()
            for needdel in needdels:
                needdel.delete()
class LossIntermediateOrderModel(models.Model):
    '''报损管理'''
    orderId = models.CharField(max_length=50, verbose_name='报损单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报损日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品报损管理'
        verbose_name_plural = '中间品报损管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = LossIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.lossintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.lossintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.loss_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class LossIntermediateDetailModel(models.Model):
    '''报损单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    loss_order = models.ForeignKey(
        LossIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报损单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='loss_details'  # 自定义反向关联名（推荐使用）
    )

    loss_num = models.DecimalField(verbose_name='报损数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品报损明细'
        verbose_name_plural = '中间品报损明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws=self.loss_order.customer.raws.all()
            old_loss_num = 0
            if not self.id:  # 新增

                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"loss_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_loss_raw = LossIntermediateDetailModel.objects.get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_loss_num = 0
            if self.id:  # 修改操作
                old_loss_raw = LossIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.loss_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SurplusIntermediateOrderModel(models.Model):
    '''报溢管理'''
    orderId = models.CharField(max_length=50, verbose_name='报溢单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报溢日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品报溢管理'
        verbose_name_plural = '中间品报溢管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SurplusIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.surplusintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.surplusintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.surplus_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SurplusIntermediateDetailModel(models.Model):
    '''报溢单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    surplus_order = models.ForeignKey(
        SurplusIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报溢单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='surplus_details'  # 自定义反向关联名（推荐使用）
    )

    surplus_num = models.DecimalField(verbose_name='报溢数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品报溢明细'
        verbose_name_plural = '中间品报溢明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.surplus_num or self.surplus_num < 0:
                raise ValidationError({"surplus_num": f"数量不能小于0，无法保存！"})
            old_surplus_num = 0
            if self.id:  # 修改
                old_surplus_raw = SurplusIntermediateDetailModel.objects.get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_surplus_num = 0
            if self.id:  # 修改操作
                old_surplus_raw = SurplusIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.surplus_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SellIntermediateOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品销售管理'
        verbose_name_plural = '中间品销售管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于销售日期！"})

            if not self.id:  # 新增
                if self.customer.state != '1':
                    raise ValidationError({"customer": f"{self.customer.name}客户不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SellIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.sell_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SellIntermediateDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sell_order = models.ForeignKey(
        SellIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='sell_details'  # 自定义反向关联名（推荐使用）
    )

    sell_num = models.DecimalField(verbose_name='数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品销售明细'
        verbose_name_plural = '中间品销售明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws = self.sell_order.customer.raws.all()
            old_sell_num = 0
            if not self.id:  # 新增

                if self.sell_order.customer.state != '1':
                    raise ValidationError({"raw": f"{self.sell_order.customer.name}客户不能停用，无法保存！"})
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sell_raw = SellIntermediateDetailModel.objects.get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw.raw not in raws:
                raise ValidationError(
                    {"raw": f"客户{self.sell_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sell_num = 0
            if self.id:  # 修改操作
                old_sell_raw = SellIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.sell_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundIntermediateOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品出库管理'
        verbose_name_plural = '中间品出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backintermediateordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundIntermediateDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='outbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品出库明细'
        verbose_name_plural = '中间品出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num = 0
            if self.id:  # 修改
                old_out_raw = OutboundIntermediateDetailModel.objects.get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundIntermediateDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backintermediateordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backintermediatedetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                        "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {
                                "outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class OtherOutboundIntermediateOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='出库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True)


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品其他出库'
        verbose_name_plural = '中间品其他出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OtherOutboundIntermediateOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.otheroutboundintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:


                # 处理关联明细，恢复库存
                details = order.otheroutboundintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class OtherOutboundIntermediateDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OtherOutboundIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
       related_name = 'otheroutbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)



    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='中间品其他出库明细'
        verbose_name_plural = '中间品其他出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})

            old_out_num=0
            if  self.id: #修改
                old_out_raw=OtherOutboundIntermediateDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''


        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OtherOutboundIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class BackIntermediateOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品退库管理'
        verbose_name_plural = '中间品退库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundintermediateordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backintermediatedetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundintermediatedetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})

                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackIntermediateDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:
                                raise ValidationError({
                                                          "product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:
                                raise ValidationError({
                                                          "product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackIntermediateOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':
                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backintermediatedetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackIntermediateDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='back_details'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品退库明细'
        verbose_name_plural = '中间品退库明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""

        # 验证主订单是否存在（有主键）
        if not self.back_order:
            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundintermediateordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundintermediatedetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackIntermediateDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:
                    raise ValidationError({
                                              "back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:
                    raise ValidationError(
                        {"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackIntermediateDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:
                    raise ValidationError(
                        {
                            "back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundIntermediateOfIntermediateProductOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品出库管理(中间品)'
        verbose_name_plural = '中间品出库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundIntermediateOfIntermediateProductOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundintermediateofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backintermediateofintermediateproductordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundintermediateofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundIntermediateOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundIntermediateOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='outbound_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品出库明细(中间品)'
        verbose_name_plural = '中间品出库明细(中间品)'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num = 0
            if self.id:  # 修改
                old_out_raw = OutboundIntermediateOfIntermediateProductDetailModel.objects.get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundIntermediateOfIntermediateProductDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backintermediateofintermediateproductordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backintermediateofintermediateproductdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                        "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {
                                "outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundIntermediateOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class BackIntermediateOfIntermediateProductOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品退库管理(中间品)'
        verbose_name_plural = '中间品退库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundintermediateofintermediateproductordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backintermediateofintermediateproductdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundintermediateofintermediateproductdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})

                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackIntermediateOfIntermediateProductDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:
                                raise ValidationError({
                                                          "product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:
                                raise ValidationError({
                                                          "product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackIntermediateOfIntermediateProductOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':
                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backintermediateofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backintermediateofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackIntermediateOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackIntermediateOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='back_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品退库明细(中间品)'
        verbose_name_plural = '中间品退库明细(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""

        # 验证主订单是否存在（有主键）
        if not self.back_order:
            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundintermediateofintermediateproductordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundintermediateofintermediateproductdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackIntermediateOfIntermediateProductDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:
                    raise ValidationError({
                                              "back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:
                    raise ValidationError(
                        {"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackIntermediateOfIntermediateProductDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:
                    raise ValidationError(
                        {
                            "back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackIntermediateOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class SellBackIntermediateOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品销售退货管理'
        verbose_name_plural = '中间品销售退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                sell_orders = self.customer.sellintermediateordermodel_set.all()
                if not sell_orders.exists():
                    raise ValidationError({"customer": f"该客户没有对应的销售记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.sellbackintermediatedetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的销售记录
                            material_sells = []
                            for order in sell_orders:
                                material_sells.extend(
                                    order.sellintermediatedetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"customer": f"物料{detail.raw.raw.name}没有对应的销售记录，无法退库！"})

                            # 计算该物料的总销售数量
                            total_sell = sum(item.sell_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = SellBackIntermediateDetailModel.objects.filter(
                                sellback_order__customer=self.customer,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.sellback_num for item in existing_back)

                            # 验证退库数量
                            if detail.sellback_num <= 0:
                                raise ValidationError({"customer": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.sellback_num > total_sell:
                                raise ValidationError({
                                    "customer": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总销售：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.sell_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                    "customer": f"退库日期不能早于物料{detail.raw.raw.name}的销售日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = SellBackIntermediateOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellbackintermediatedetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellbackrawdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.sellback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SellBackIntermediateDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sellback_order = models.ForeignKey(
        SellBackIntermediateOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreIntermediateDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='中间品',
        related_name='sellback_details'  # 自定义反向关联名（推荐使用）
    )

    sellback_num = models.DecimalField(verbose_name='退货数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '中间品销售退货明细'
        verbose_name_plural = '中间品销售退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.sellback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws = self.sellback_order.customer.raws.all()
            old_sellback_num = 0
            if not self.id:  # 新增

                if self.sellback_order.customer.state != '1':
                    raise ValidationError({"raw": f"{self.sellback_order.customer.name}客户不能停用，无法保存！"})
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sellback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sellback_raw = SellBackIntermediateDetailModel.objects.get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw.raw not in raws:
                raise ValidationError(
                    {"raw": f"客户{self.sellback_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            sell_orders = self.sellback_order.customer.sellintermediateordermodel_set.all()
            material_sells = []
            for order in sell_orders:
                material_sells.extend(
                    order.sellintermediatedetailmodel_set.filter(raw=self.raw)
                )

            if not material_sells:

                raise ValidationError({
                                          "raw": f"客户{self.sellback_order.customer.name} 物料{self.raw.raw.name}没有对应的销售记录，无法退库！"})
            else:
                # 计算总销售量
                total_sell = sum(item.sell_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = SellBackIntermediateDetailModel.objects.filter(
                    sellback_order__customer=self.sellback_order.customer,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.sellback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.sellback_num > total_sell:
                    raise ValidationError({
                        "sellback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.sellback_num}，总销售：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.sell_order.date for item in material_sells)
                if self.sellback_order.date < earliest_sell_date:
                    raise ValidationError({
                        "sellback_num": f"退库日期不能早于物料{self.raw.raw.name}的销售日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sellback_num = 0
            if self.id:  # 修改操作
                old_sellback_raw = SellBackIntermediateDetailModel.objects.select_for_update().get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw_id:
                storeRawDetail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.sellback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreIntermediateDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()





class StoreFinishedOrderModel(models.Model):
    '''物料入库'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='入库日期', default=timezone.now)
    TYPE_CHOICES = [
        ("1", "入库单"),
        ("0", "期初入库单"),
    ]
    type = models.CharField(max_length=50, verbose_name='类型', default='1', choices=TYPE_CHOICES)
    # 将原来的standard字符字段改为外键关联到StandardModel
    # supplier = models.ForeignKey(
    #     baseModels.SupplierRawModel,
    #     on_delete=models.PROTECT,
    #     null=False,  # 数据库层面不允许为NULL
    #     blank=False,
    #     verbose_name='供应商',
    # )
    SUPPLIER_CHOICES = [
        ("1", "植物饮料"),
        ("0", "蛋白饮料"),
        ("2", "复配食品添加剂"),
    ]
    supplier = models.CharField(max_length=50, verbose_name='类型', default='1', choices=SUPPLIER_CHOICES)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品入库管理'
        verbose_name_plural = '成品入库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # if not self.supplier:
            #     raise ValidationError({"supplier": f"没有选择供应商！"})
            # if self.supplier.exp_date < self.date:
            #     raise ValidationError({"supplier": f"供应商有效期小于入库日期！"})

            if not self.id:  # 新增
                # if self.supplier.state != '1':
                #     raise ValidationError({"supplier": f"{self.supplier.name}供应商不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = StoreFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)


class StoreFinishedOrderFileModel(models.Model):
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 先定义动态 upload_to 函数（关键）
    def dynamic_upload_path(instance, filename):
        """
        动态生成文件上传路径
        格式: uploads/files/report/[用户ID]/[应用标签]/[模型名称]/[文件名]
        """
        # # 获取模型元数据
        # code = instance.code  # 应用标签
        #
        # # 获取用户ID（确保模型有user字段关联）
        # user_id = instance.user.id

        # 拼接路径（将特殊字符替换为下划线，避免路径错误）
        return f"uploads/files/store/finished/{instance.pk}/{filename}"

    file = models.ImageField(upload_to=dynamic_upload_path, validators=[validate_file_size],
                             verbose_name='附件')

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '附件'
        verbose_name_plural = '附件'  # 自定义复数形式，与单数相同


class StoreFinishedDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    store_order = models.ForeignKey(
        StoreFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='入库单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        baseModels.FinishedModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    location = models.ForeignKey(
        baseModels.LocationFinishedModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details'  # 自定义反向关联名（推荐使用）
    )
    batch_number = models.CharField(max_length=50, verbose_name='批号', default=None, null=False,  # 数据库层面不允许为NULL
                                    blank=False)
    # 将原来的standard字符字段改为外键关联到StandardModel
    unit = models.ForeignKey(
        baseModels.UnitModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='单位',
        # related_name='store_details_unit'  # 自定义反向关联名（推荐使用）
    )

    store_num = models.DecimalField(verbose_name='数量', max_digits=10, decimal_places=2)
    price = models.DecimalField(verbose_name='单价', max_digits=20, decimal_places=13, default=0)
    tax = models.DecimalField(verbose_name='税率%', max_digits=3, decimal_places=0, default=13)
    remaining_num = models.DecimalField(verbose_name='剩余数量', max_digits=10, decimal_places=2, default=0)
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)
    parent = models.ForeignKey(
        'self',
        on_delete=models.PROTECT,
        null=True,  # 数据库层面不允许为NULL
        blank=True,
        default=None,
        verbose_name='父级',
    )
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品入库明细'
        verbose_name_plural = '成品入库明细'  # 自定义复数形式，与单数相同
        # 自定义权限：格式为("权限代码", "权限描述")
        permissions = [
            # ("can_approve_order", "能审核入库单"),  # 审核权限
            # ("can_export_order", "能导出入库单数据"),  # 导出权限
            ("view_subledger_storefinisheddetailmodel", "能查看明细账"),  # 统计查看权限
        ]

    def __str__(self):
        result = '<' + '>  <'.join(
            [self.raw.name, self.batch_number, '剩' + str(self.remaining_num) + str(self.unit.unit)]) + '>'
        return result

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws = self.store_order.supplier.raws.all()

            if not self.id:  # 新增
                # if self.raw.state != '1':
                #     raise ValidationError({"raw": f"{self.raw.name}物料不能停用，无法保存！"})
                # if self.store_order.supplier.state != '1':
                #     raise ValidationError({"raw": f"{self.store_order.supplier.name}供应商不能停用，无法保存！"})
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                self.remaining_num = self.store_num
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            else:
                if not self.store_num or self.store_num < 0:
                    raise ValidationError({"store_num": f"数量不能小于0，无法保存！"})
                old_store_num = StoreFinishedDetailModel.objects.filter(id=self.id).values_list('store_num').first()

                self.remaining_num = self.store_num - old_store_num[0] + self.remaining_num
                if self.remaining_num < 0:
                    raise ValidationError(
                        {"store_num": f"剩余数量不能小于0，无法修改保存！改后剩余数{self.remaining_num}"})
                # 验证入库的是货位还是库区，如果是货位要保证这个货位只能有他
                if self.location.type == '0':
                    store_details = self.location.store_details.filter(remaining_num__gt=0).exclude(
                        id=self.id if self.id else None).all()
                    if store_details.count() > 0:
                        raise ValidationError({"location": f"该货位已有货物，无法保存！"})
            # if self.raw not in raws:
            #     raise ValidationError(
            #         {"raw": f"供应商{self.store_order.supplier.name}不供应{self.raw.name}物料，无法保存！"})

        except Exception as e:
            raise ValidationError(e)

            # raise  ValidationError({"raw": f"{e}错误！"})

class TransferFinishedOrderModel(models.Model):
    '''物料调拨'''
    orderId = models.CharField(max_length=50, verbose_name='入库单号', default='',null=False,  # 数据库层面不允许为NULL
        blank=True,unique=True)
    date = models.DateField(verbose_name='调拨日期', default=timezone.now)


    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='成品调拨管理'
        verbose_name_plural = '成品调拨管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId



    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 执行调拨操作
                for detail in self.transferfinisheddetailmodel_set.all():
                    # 减少源货位库存
                    from_store = detail.from_store_detail
                    if from_store.remaining_num-detail.transfer_num < 0:
                        raise ValidationError(f"源货位库存不足: {from_store}")
                    # 检查目标货位类型，如果是货位，确保没有其他库存
                    if detail.to_location.type == '0':  # 货位
                        existing = detail.to_location.store_details.filter(remaining_num__gt=0).exclude(
                            id=detail.to_store_detail.id if detail.to_store_detail else None
                        ).exists()
                        if existing:
                            raise ValidationError(f"目标货位已有其他货物: {detail.to_location}")


            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = TransferFinishedOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})

        except Exception as e:
            raise ValidationError(e)
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)
    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # # 检查生产品种状态，不允许删除已保存品种的出库单
        # if self.product.state == '1':
        #     raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.transferfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                # store_raw_detail.save()
                detail.delete()
                #删除对应的入库数据
                # detail.to_store_detail.delete()
            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            # details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.transferfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.from_store_detail_id)
                    store_raw_detail.remaining_num += detail.transfer_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    # store_raw_detail.save()
                    # 要先删除明细
                    detail.delete()
                    # # 删除对应的入库数据
                    # detail.to_store_detail.delete()
                # 删除明细
                # details.delete()

            # 批量删除主单
            queryset.delete()
class TransferFinishedDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    transfer_order = models.ForeignKey(
        TransferFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨单号'
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    from_store_detail = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨前成品',
        related_name = 'store_details_before'  # 自定义反向关联名（推荐使用）
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    to_location = models.ForeignKey(
        baseModels.LocationFinishedModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='货位',
        related_name='store_details_transfer'  # 自定义反向关联名（推荐使用）
    )

    # 将原来的standard字符字段改为外键关联到StandardModel
    to_store_detail = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='调拨后成品',
        related_name='store_details_after'  # 自定义反向关联名（推荐使用）
    )

    transfer_num = models.DecimalField(verbose_name='调拨数量', max_digits=10,decimal_places=2)

    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='成品调拨明细'
        verbose_name_plural = '成品调拨明细'  # 自定义复数形式，与单数相同




    def clean(self):
        # 验证源和目标不能相同
        if self.from_store_detail.location == self.to_location:
            raise ValidationError({"to_location":"源货位和目标货位不能相同"})

        # 验证调拨数量
        if not self.transfer_num or self.transfer_num <= 0:
            raise ValidationError("调拨数量必须大于0")

        # 验证源货位有足够库存
        if self.from_store_detail.remaining_num < self.transfer_num:
            raise ValidationError("调拨数量不能大于源货位剩余数量")

        # 验证目标货位类型限制
        if self.to_location.type == '0':  # 货位
            # 检查目标货位是否已有其他库存
            try:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exclude(
                    id=self.to_store_detail.id if self.to_store_detail else None
                ).exists()
            except:
                existing = self.to_location.store_details.filter(remaining_num__gt=0).exists()
            if existing:
                raise ValidationError("目标货位为库位，已有其他货物，不能再调入")

        # # 如果指定了目标库存记录，验证是否匹配
        # if self.to_store_detail and self.to_store_detail.location != self.to_location:
        #     raise ValidationError("目标库存记录与目标货位不匹配")
        #
        # if self.to_store_detail and self.to_store_detail.raw != self.from_store_detail.raw:
        #     raise ValidationError("目标库存记录与源库存记录的物料不同")

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        # self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_transfer_num = 0
            if self.pk:  # 修改操作
                old_transfer_raw = TransferFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_transfer_num = old_transfer_raw.transfer_num
            if self.from_store_detail_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.transfer_num + old_transfer_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()
            try:
                if self.to_store_detail :
                    # 更新现有库存记录
                    to_store = self.to_store_detail
                    to_store.remaining_num = to_store.remaining_num+self.transfer_num-old_transfer_num
                    to_store.save()

            except:
                # 创建新的库存记录
                to_store = StoreFinishedDetailModel(
                    store_order=self.from_store_detail.store_order,  # 调拨产生的库存没有对应的入库单
                    raw=self.from_store_detail.raw,
                    location=self.to_location,
                    batch_number=self.from_store_detail.batch_number,
                    unit=self.from_store_detail.unit,
                    store_num=self.transfer_num,
                    remaining_num=self.transfer_num,
                    parent=self.from_store_detail
                )
                to_store.save()
                self.to_store_detail = to_store


            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.from_store_detail_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.transfer_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()


            # 执行实际删除操作
            super().delete(*args, **kwargs)
            self.to_store_detail.delete()
    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            needdels=[]
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update     ().get(id=detail.from_store_detail_id)
                store_raw_detail.remaining_num += detail.transfer_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！{detail.to_store_detail}")
                store_raw_detail.save()
                needdels.append(detail.to_store_detail)


            # 批量删除记录
            queryset.delete()
            for needdel in needdels:
                needdel.delete()
class LossFinishedOrderModel(models.Model):
    '''报损管理'''
    orderId = models.CharField(max_length=50, verbose_name='报损单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报损日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=True, blank=True)
    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品报损管理'
        verbose_name_plural = '成品报损管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = LossFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.lossfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.lossfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.loss_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class LossFinishedDetailModel(models.Model):
    '''报损单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    loss_order = models.ForeignKey(
        LossFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报损单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='loss_details'  # 自定义反向关联名（推荐使用）
    )

    loss_num = models.DecimalField(verbose_name='报损数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品报损明细'
        verbose_name_plural = '成品报损明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # raws=self.loss_order.customer.raws.all()
            old_loss_num = 0
            if not self.id:  # 新增

                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"loss_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.loss_num or self.loss_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_loss_raw = LossFinishedDetailModel.objects.get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_loss_num = 0
            if self.id:  # 修改操作
                old_loss_raw = LossFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_loss_num = old_loss_raw.loss_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.loss_num + old_loss_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.loss_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.loss_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SurplusFinishedOrderModel(models.Model):
    '''报溢管理'''
    orderId = models.CharField(max_length=50, verbose_name='报溢单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='报溢日期', default=timezone.now)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品报溢管理'
        verbose_name_plural = '成品报溢管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:

            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SurplusFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.surplusfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.surplusfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.surplus_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SurplusFinishedDetailModel(models.Model):
    '''报溢单'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    surplus_order = models.ForeignKey(
        SurplusFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='报溢单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='surplus_details'  # 自定义反向关联名（推荐使用）
    )

    surplus_num = models.DecimalField(verbose_name='报溢数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品报溢明细'
        verbose_name_plural = '成品报溢明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.surplus_num or self.surplus_num < 0:
                raise ValidationError({"surplus_num": f"数量不能小于0，无法保存！"})
            old_surplus_num = 0
            if self.id:  # 修改
                old_surplus_raw = SurplusFinishedDetailModel.objects.get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})




        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_surplus_num = 0
            if self.id:  # 修改操作
                old_surplus_raw = SurplusFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_surplus_num = old_surplus_raw.surplus_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.surplus_num - old_surplus_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.surplus_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.surplus_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class SellFinishedOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerFinishedModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品销售管理'
        verbose_name_plural = '成品销售管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于销售日期！"})

            if not self.id:  # 新增
                if self.customer.state != '1':
                    raise ValidationError({"customer": f"{self.customer.name}客户不能停用，无法保存！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = SellFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '订单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '订单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.sell_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SellFinishedDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sell_order = models.ForeignKey(
        SellFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='sell_details'  # 自定义反向关联名（推荐使用）
    )

    sell_num = models.DecimalField(verbose_name='数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品销售明细'
        verbose_name_plural = '成品销售明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            raws = self.sell_order.customer.raws.all()
            old_sell_num = 0
            if not self.id:  # 新增

                if self.sell_order.customer.state != '1':
                    raise ValidationError({"raw": f"{self.sell_order.customer.name}客户不能停用，无法保存！"})
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sell_num or self.sell_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sell_raw = SellFinishedDetailModel.objects.get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw.raw not in raws:
                raise ValidationError(
                    {"raw": f"客户{self.sell_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"数量大于库存数量，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sell_num = 0
            if self.id:  # 修改操作
                old_sell_raw = SellFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_sell_num = old_sell_raw.sell_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.sell_num + old_sell_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.sell_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.sell_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundFinishedOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品出库管理'
        verbose_name_plural = '成品出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backfinishedordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundFinishedDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='outbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品出库明细'
        verbose_name_plural = '成品出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num = 0
            if self.id:  # 修改
                old_out_raw = OutboundFinishedDetailModel.objects.get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundFinishedDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backfinishedordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backfinisheddetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                        "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {
                                "outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()

class OtherOutboundFinishedOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='出库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    remark = models.CharField(max_length=255, verbose_name='备注', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True)


    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品其他出库'
        verbose_name_plural = '成品其他出库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:


            if not self.id:  # 新增

                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OtherOutboundFinishedOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""


        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.otheroutboundfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:


                # 处理关联明细，恢复库存
                details = order.otheroutboundfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()
class OtherOutboundFinishedDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OtherOutboundFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
       related_name = 'otheroutbound_details'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10,decimal_places=2)



    create_at = models.DateTimeField(verbose_name='创建日期',default=timezone.now ) # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)
    class Meta:
        verbose_name='成品其他出库明细'
        verbose_name_plural = '成品其他出库明细'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})

            old_out_num=0
            if  self.id: #修改
                old_out_raw=OtherOutboundFinishedDetailModel.objects.get(id=self.id)
                old_out_num=old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail=StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num=storeRawDetail.remaining_num-self.outbound_num+old_out_num
                if remaining_num<0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''


        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OtherOutboundFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""


        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:


                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class BackFinishedOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品退库管理'
        verbose_name_plural = '成品退库管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundfinishedordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backfinisheddetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundfinisheddetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})

                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackFinishedDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:
                                raise ValidationError({
                                                          "product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:
                                raise ValidationError({
                                                          "product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackFinishedOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':
                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackFinishedDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='back_details'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品退库明细'
        verbose_name_plural = '成品退库明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""

        # 验证主订单是否存在（有主键）
        if not self.back_order:
            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundfinishedordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundfinisheddetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackFinishedDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:
                    raise ValidationError({
                                              "back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:
                    raise ValidationError(
                        {"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackFinishedDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreFinishedDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:
                    raise ValidationError(
                        {
                            "back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class OutboundFinishedOfIntermediateProductOrderModel(models.Model):
    '''出库管理'''
    orderId = models.CharField(max_length=50, verbose_name='领料单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='出库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品出库管理(中间品)'
        verbose_name_plural = '成品出库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            if self.product.date > self.date:
                raise ValidationError({"product": f"生产指令日期大于物料出库日期！"})

            if not self.id:  # 新增
                if self.product.state == '1':
                    raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能再领用任何物料！"})
                if not self.orderId:

                    id_date = datetime.now()
                    if self.date:
                        id_date = self.date
                    orderIdList = OutboundFinishedOfIntermediateProductOrderModel.objects.filter(
                        orderId__startswith=f'{id_date.strftime("%Y%m%d")}').order_by('-orderId').values_list('orderId',
                                                                                                              flat=True).first()

                    if orderIdList:
                        try:
                            result = orderIdList[:7] + str(int(orderIdList[7:]) + 1).zfill(4)
                        except:
                            raise ValidationError({"orderId": '订单编号检验失败'})
                    else:
                        result = id_date.strftime('%Y%m%d') + '0001'
                    # print(raw)
                    self.orderId = result

            try:
                if len(self.orderId) == 12:
                    int(self.orderId[7:])
                else:
                    raise ValidationError({"orderId": '出库单编号检验失败,订单编号只能保留12位'})
            except:
                raise ValidationError({"orderId": '出库单编号检验失败,订单编号格式不对'})




        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.outboundfinishedofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")
                # 删除时要查看是否有退库的物料
                backs = order.product.backfinishedofintermediateproductordermodel_set.all()
                if backs:
                    raise ValidationError(f"有退库，不能删除出库记录！")
                # 处理关联明细，恢复库存
                details = order.outboundfinishedofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num += detail.outbound_num
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class OutboundFinishedOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    out_order = models.ForeignKey(
        OutboundFinishedOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='出库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='outbound_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    outbound_num = models.DecimalField(verbose_name='出库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品出库明细(中间品)'
        verbose_name_plural = '成品出库明细(中间品)'  # 自定义复数形式，与单数相同
        # 设置默认排序字段，比如按 id 升序排列
        ordering = ['id']

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.outbound_num or self.outbound_num < 0:
                raise ValidationError({"outbound_num": f"出库数量不能小于0，无法保存！"})
            if self.out_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.out_order.product.name}已经保存，不能再出库物料！"})
            old_out_num = 0
            if self.id:  # 修改
                old_out_raw = OutboundFinishedOfIntermediateProductDetailModel.objects.get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"outbound_num": f"数量大于库存数量，无法保存！"})
                '''验证出库数量大于退库数量 ，验证出库日期在退库日期前面
                                1.获取所有该品种的出库数量和
                                2.获取所有该品种的退库数量和

                                '''
                # 计算已出库量（不包括当前记录）
                existing_out = OutboundFinishedOfIntermediateProductDetailModel.objects.filter(
                    out_order__product=self.out_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_outed = sum(item.outbound_num for item in existing_out)
                # 获取该物料的退库记录
                back_orders = self.out_order.product.backfinishedofintermediateproductordermodel_set.all()
                material_backs = []
                for order in back_orders:
                    material_backs.extend(
                        order.backfinishedofintermediateproductdetailmodel_set.filter(raw=self.raw)
                    )
                # 计算总领料量
                total_back = sum(item.back_num for item in material_backs)

                # 验证出库总量
                if total_outed + self.outbound_num < total_back:
                    raise ValidationError({
                        "outbound_num": f"物料{self.raw.raw.name}出库数量小于退库数量！已退：{total_back}，本次出库：{self.outbound_num}，总出库：{total_outed}"})
                # 验证出库日期
                if material_backs:
                    earliest_back_date = min(item.back_order.date for item in material_backs)
                    if self.out_order.date > earliest_back_date:
                        raise ValidationError(
                            {
                                "outbound_num": f"领料日期不能早于物料{self.raw.raw.name}的退料日期({earliest_back_date})！"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_out_num = 0
            if self.id:  # 修改操作
                old_out_raw = OutboundFinishedOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_out_num = old_out_raw.outbound_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num - self.outbound_num + old_out_num
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.out_order.product.state == '1':
            raise ValidationError(f"生产品种{self.out_order.product.name}已经保存，不能删除出库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num += self.outbound_num
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.out_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除出库记录！")

                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num += detail.outbound_num
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()


class BackFinishedOfIntermediateProductOrderModel(models.Model):
    '''退库管理'''
    orderId = models.CharField(max_length=50, verbose_name='退库单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='退库日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    product = models.ForeignKey(
        productionModels.ProductOrderOfIntermediateModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='生产品种',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品退库管理(中间品)'
        verbose_name_plural = '成品退库管理(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            # 验证生产品种是否已选择
            if not self.product:
                raise ValidationError({"product": f"没有选择生产品种！"})
            # 新增时（无主键）不验证明细关联，只验证主单信息
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                outbound_orders = self.product.outboundfinishedofintermediateproductordermodel_set.all()
                if not outbound_orders.exists():
                    raise ValidationError({"product": f"该生产品种没有对应的领料记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.backfinishedofintermediateproductdetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的领料记录
                            material_outbounds = []
                            for order in outbound_orders:
                                material_outbounds.extend(
                                    order.outboundfinishedofintermediateproductdetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_outbounds:
                                raise ValidationError({"product": f"物料{detail.raw.name}没有对应的领料记录，无法退库！"})

                            # 计算该物料的总领料数量
                            total_outbound = sum(item.outbound_num for item in material_outbounds)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = BackFinishedOfIntermediateProductDetailModel.objects.filter(
                                back_order__product=self.product,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.back_num for item in existing_back)

                            # 验证退库数量
                            if detail.back_num <= 0:
                                raise ValidationError({"product": f"物料{detail.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.back_num > total_outbound:
                                raise ValidationError({
                                                          "product": f"物料{detail.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.back_num}，总领料：{total_outbound}"})

                            # 验证退库日期是否早于领料日期
                            earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                            if self.date and self.date < earliest_outbound_date:
                                raise ValidationError({
                                                          "product": f"退库日期不能早于物料{detail.raw.name}的领料日期({earliest_outbound_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = BackFinishedOfIntermediateProductOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

            # 验证订单号格式
            if self.orderId:
                if len(self.orderId) != 12:
                    raise ValidationError({"orderId": f"订单编号必须为12位！"})

                else:
                    try:
                        # 验证前8位是日期，后4位是数字
                        datetime.strptime(self.orderId[:8], "%Y%m%d")
                        int(self.orderId[8:])
                    except ValueError:

                        raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})
            # 验证生产品种状态
            if self.product and self.product.state == '1':
                raise ValidationError({"product": f"生产品种{self.product.name}已经保存，不能进行退库操作！"})
        except Exception as e:
            raise ValidationError(e)

    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()
        # 保存主记录（获取主键）
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""
        # 检查生产品种状态，不允许删除已保存品种的出库单
        if self.product.state == '1':
            raise ValidationError(f"生产品种{self.product.name}已经保存，不能删除出库单！")

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.backfinishedofintermediateproductdetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:
                # 检查状态
                if order.product.state == '1':
                    raise ValidationError(f"生产品种{order.product.name}已经保存，不能删除出库单！")

                # 处理关联明细，恢复库存
                details = order.backfinishedofintermediateproductdetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.back_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class BackFinishedOfIntermediateProductDetailModel(models.Model):
    '''物料出库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    back_order = models.ForeignKey(
        BackFinishedOfIntermediateProductOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='退库单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='back_details_intermediate_product'  # 自定义反向关联名（推荐使用）
    )

    back_num = models.DecimalField(verbose_name='退库数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品退库明细(中间品)'
        verbose_name_plural = '成品退库明细(中间品)'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        """在明细级别进行验证，确保主记录已存在"""

        # 验证主订单是否存在（有主键）
        if not self.back_order:
            raise ValidationError({"raw": '请先保存退库单主记录！'})

        try:
            if not self.back_num or self.back_num < 0:
                raise ValidationError({"back_num": f"退库数量不能小于0，无法保存！"})
            if self.back_order.product.state == '1':
                raise ValidationError({"raw": f"生产品种{self.back_order.product.name}已经保存，不能再退回物料！"})

            # 获取该物料的领料记录
            outbound_orders = self.back_order.product.outboundfinishedofintermediateproductordermodel_set.all()
            material_outbounds = []
            for order in outbound_orders:
                material_outbounds.extend(
                    order.outboundfinishedofintermediateproductdetailmodel_set.filter(raw=self.raw)
                )

            if not material_outbounds:

                raise ValidationError({"raw": f"物料{self.raw.raw.name}没有对应的领料记录，无法退库！"})
            else:
                # 计算总领料量
                total_outbound = sum(item.outbound_num for item in material_outbounds)
                # 计算已退库量（不包括当前记录）
                existing_back = BackFinishedOfIntermediateProductDetailModel.objects.filter(
                    back_order__product=self.back_order.product,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.back_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.back_num > total_outbound:
                    raise ValidationError({
                                              "back_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.back_num}，总领料：{total_outbound}"})
                # 验证退库日期
                earliest_outbound_date = min(item.out_order.date for item in material_outbounds)
                if self.back_order.date < earliest_outbound_date:
                    raise ValidationError(
                        {"back_num": f"退库日期不能早于物料{self.raw.raw.name}的领料日期({earliest_outbound_date})！"})
            # 验证库存是否充足
            if self.raw_id:
                old_back_num = 0
                if self.id:  # 修改操作
                    old_detail = BackFinishedOfIntermediateProductDetailModel.objects.get(id=self.id)
                    old_back_num = old_detail.back_num

                store_raw_detail = StoreFinishedDetailModel.objects.get(id=self.raw_id)
                remaining_num = store_raw_detail.remaining_num + self.back_num - old_back_num
                if remaining_num < 0:
                    raise ValidationError(
                        {
                            "back_num": f"库存不足，无法退库！当前库存: {store_raw_detail.remaining_num}，已退库{old_back_num}"})

        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_back_num = 0
            if self.id:  # 修改操作
                old_back_raw = BackFinishedOfIntermediateProductDetailModel.objects.select_for_update().get(id=self.id)
                old_back_num = old_back_raw.back_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.back_num - old_back_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""
        # 检查生产品种状态，不允许删除已保存的品种的出库记录
        if self.back_order.product.state == '1':
            raise ValidationError(f"生产品种{self.back_order.product.name}已经保存，不能删除退库记录！")

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.back_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:
                # 检查状态
                if detail.back_order.product.state == '1':
                    raise ValidationError(f"生产品种{detail.out_order.product.name}已经保存，不能删除记录！")

                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.back_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()
class SellBackFinishedOrderModel(models.Model):
    orderId = models.CharField(max_length=50, verbose_name='销售退货单号', default='', null=False,  # 数据库层面不允许为NULL
                               blank=True, unique=True)
    date = models.DateField(verbose_name='销售退货日期', default=timezone.now)
    # 将原来的standard字符字段改为外键关联到StandardModel
    customer = models.ForeignKey(
        baseModels.CustomerFinishedModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='客户',
    )
    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品销售退货管理'
        verbose_name_plural = '成品销售退货管理'  # 自定义复数形式，与单数相同

    def __str__(self):
        return self.orderId

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        try:
            if not self.customer:
                raise ValidationError({"customer": f"没有选择客户！"})
            if self.customer.exp_date < self.date:
                raise ValidationError({"customer": f"客户有效期应大于退货日期！"})
            # 明细验证将在明细保存时进行
            if self.pk:  # 只有当主记录已保存（有主键）时才验证明细关联
                # 获取该产品的所有领料记录
                sell_orders = self.customer.sellfinishedordermodel_set.all()
                if not sell_orders.exists():
                    raise ValidationError({"customer": f"该客户没有对应的销售记录，无法退库！"})
                else:
                    # 验证每个退库明细,原来的退库明细
                    back_details = self.sellbackfinisheddetailmodel_set.all()

                    # 新增时可能还没有明细，此时不验证明细，但可以提示需要添加明细
                    if not back_details.exists():  # 无明细
                        pass
                        # errors["__all__"] = errors.get("__all__", []) + ["请添加至少一条退库明细记录！"]
                    else:
                        for detail in back_details:
                            # 查找该物料对应的销售记录
                            material_sells = []
                            for order in sell_orders:
                                material_sells.extend(
                                    order.sellfinisheddetailmodel_set.filter(raw=detail.raw)
                                )

                            if not material_sells:
                                raise ValidationError(
                                    {"customer": f"物料{detail.raw.raw.name}没有对应的销售记录，无法退库！"})

                            # 计算该物料的总销售数量
                            total_sell = sum(item.sell_num for item in material_sells)
                            # 计算已退库数量（不包括当前正在编辑的记录）
                            existing_back = SellBackFinishedDetailModel.objects.filter(
                                sellback_order__customer=self.customer,
                                raw=detail.raw
                            ).exclude(id=detail.id if detail.id else None)
                            total_backed = sum(item.sellback_num for item in existing_back)

                            # 验证退库数量
                            if detail.sellback_num <= 0:
                                raise ValidationError({"customer": f"物料{detail.raw.raw.name} 退库数量必须大于0！"})
                            # 验证退库总量是否超过可退数量
                            if total_backed + detail.sellback_num > total_sell:
                                raise ValidationError({
                                    "customer": f"物料{detail.raw.raw.name} 退库数量超过可退数量，已退：{total_backed}，本次退库：{detail.sellback_num}，总销售：{total_sell}"})

                            # 验证退库日期是否早于领料日期
                            earliest_sell_date = min(item.sell_order.date for item in material_sells)
                            if self.date and self.date < earliest_sell_date:
                                raise ValidationError({
                                    "customer": f"退库日期不能早于物料{detail.raw.raw.name}的销售日期({earliest_sell_date})"})

            # 新增时自动生成订单号

            if not self.pk and not self.orderId:  # 新建
                id_date = self.date if self.date else datetime.now().date()
                prefix = id_date.strftime("%Y%m%d")

                # 获取当天最大订单号
                last_order = SellBackFinishedOrderModel.objects.filter(
                    orderId__startswith=prefix
                ).order_by('-orderId').first()

                if last_order:
                    try:
                        seq = int(last_order.orderId[8:]) + 1  # 假设日期是8位：YYYYMMDD
                        self.orderId = f"{prefix}{seq:04d}"
                    except (ValueError, IndexError):

                        raise ValidationError({"orderId": f"订单编号生成失败，格式错误！"})
                else:
                    self.orderId = f"{prefix}0001"

                # 验证订单号格式
                if self.orderId:
                    if len(self.orderId) != 12:
                        raise ValidationError({"orderId": f"订单编号必须为12位！"})

                    else:
                        try:
                            # 验证前8位是日期，后4位是数字
                            datetime.strptime(self.orderId[:8], "%Y%m%d")
                            int(self.orderId[8:])
                        except ValueError:

                            raise ValidationError({"orderId": f"订单编号格式错误，应为YYYYMMDDNNNN！"})


        except Exception as e:

            raise ValidationError(e)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除主单时级联删除明细细并恢复库存"""

        # 使用事务确保所有操作的原子性
        with transaction.atomic():
            # 1. 获取当前出库单关联的所有明细
            details = self.sellbackfinisheddetailmodel_set.all()

            # 2. 遍历明细，恢复库存
            for detail in details:
                # 锁定库存记录录防止并发问题
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                # 恢复库存
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 3. 先删除所有关联的明细（也会触发明细自身的delete方法）
            details.delete()

            # 4. 最后删除主单
            super().delete(*args, **kwargs)

    @classmethod
    def bulk_delete(cls, queryset):
        """批量删除出库单并恢复库存"""
        with transaction.atomic():
            for order in queryset:

                # 处理关联明细，恢复库存
                details = order.sellbackfinisheddetailmodel_set.all()
                for detail in details:
                    store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                    store_raw_detail.remaining_num -= detail.sellback_num
                    if store_raw_detail.remaining_num < 0:
                        raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                    store_raw_detail.save()

                # 删除明细
                details.delete()

            # 批量删除主单
            queryset.delete()


class SellBackFinishedDetailModel(models.Model):
    '''物料入库'''
    # 将原来的standard字符字段改为外键关联到StandardModel
    sellback_order = models.ForeignKey(
        SellBackFinishedOrderModel,
        on_delete=models.CASCADE,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='销售退货单号'
    )
    # 将原来的standard字符字段改为外键关联到StandardModel
    raw = models.ForeignKey(
        StoreFinishedDetailModel,
        on_delete=models.PROTECT,
        null=False,  # 数据库层面不允许为NULL
        blank=False,
        verbose_name='成品',
        related_name='sellback_details'  # 自定义反向关联名（推荐使用）
    )

    sellback_num = models.DecimalField(verbose_name='退货数量', max_digits=10, decimal_places=2)

    # 创建时间：首次保存时自动设置为当前时间，之后不会更新
    # create_at = models.DateTimeField(verbose_name='创建日期', auto_now_add=True)

    create_at = models.DateTimeField(verbose_name='创建日期', default=timezone.now)  # 默认为当前时间
    # 更新时间：每次保存（包括首次）时自动更新为当前时间
    update_at = models.DateTimeField(verbose_name='更新日期', auto_now=True)

    class Meta:
        verbose_name = '成品销售退货明细'
        verbose_name_plural = '成品销售退货明细'  # 自定义复数形式，与单数相同

    def __str__(self):
        return '|'.join([self.raw.raw.name])

    def clean(self):
        """模型验证方法，用于检查保存条件"""
        # 验证主订单是否存在（有主键）
        if not self.sellback_order:
            raise ValidationError({"raw": '请先保存销售退货单主记录！'})
        try:
            raws = self.sellback_order.customer.raws.all()
            old_sellback_num = 0
            if not self.id:  # 新增

                if self.sellback_order.customer.state != '1':
                    raise ValidationError({"raw": f"{self.sellback_order.customer.name}客户不能停用，无法保存！"})
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sellback_num": f"数量不能小于0，无法保存！"})

            else:
                if not self.sellback_num or self.sellback_num < 0:
                    raise ValidationError({"sell_num": f"数量不能小于0，无法保存！"})
                old_sellback_raw = SellBackFinishedDetailModel.objects.get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw.raw not in raws:
                raise ValidationError(
                    {"raw": f"客户{self.sellback_order.customer.name}不能销售{self.raw.raw.name}物料，无法保存！"})

            # 获取该客户的销售记录
            sell_orders = self.sellback_order.customer.sellfinishedordermodel_set.all()
            material_sells = []
            for order in sell_orders:
                material_sells.extend(
                    order.sellfinisheddetailmodel_set.filter(raw=self.raw)
                )

            if not material_sells:

                raise ValidationError({
                                          "raw": f"客户{self.sellback_order.customer.name} 物料{self.raw.raw.name}没有对应的销售记录，无法退库！"})
            else:
                # 计算总销售量
                total_sell = sum(item.sell_num for item in material_sells)
                # 计算已退库量（不包括当前记录）
                existing_back = SellBackFinishedDetailModel.objects.filter(
                    sellback_order__customer=self.sellback_order.customer,
                    raw=self.raw
                ).exclude(id=self.id if self.id else None)
                total_backed = sum(item.sellback_num for item in existing_back)

                # 验证退库总量
                if total_backed + self.sellback_num > total_sell:
                    raise ValidationError({
                        "sellback_num": f"物料{self.raw.raw.name}退库数量超过可退数量！已退：{total_backed}，本次退库：{self.sellback_num}，总销售：{total_sell}"})
                # 验证退库日期
                earliest_sell_date = min(item.sell_order.date for item in material_sells)
                if self.sellback_order.date < earliest_sell_date:
                    raise ValidationError({
                        "sellback_num": f"退库日期不能早于物料{self.raw.raw.name}的销售日期({earliest_sell_date})！"})

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.get(id=self.raw_id)

                remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if remaining_num < 0:
                    raise ValidationError(
                        {"sell_num": f"库存数量小于o，无法保存！"})





        except Exception as e:
            raise ValidationError(e)

    # 重写模型的save方法，使用事务管理
    def save(self, *args, **kwargs):
        # 先执行验证
        self.full_clean()

        # 使用事务确保数据一致性
        with transaction.atomic():
            # 执行库存更新
            old_sellback_num = 0
            if self.id:  # 修改操作
                old_sellback_raw = SellBackFinishedDetailModel.objects.select_for_update().get(id=self.id)
                old_sellback_num = old_sellback_raw.sellback_num

            if self.raw_id:
                storeRawDetail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)
                storeRawDetail.remaining_num = storeRawDetail.remaining_num + self.sellback_num - old_sellback_num
                if storeRawDetail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                storeRawDetail.save()

            # 保存当前模型
            super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """重写删除方法，删除时恢复库存"""

        # 使用事务确保库存恢复和记录删除的原子性
        with transaction.atomic():
            # 锁定相关库存记录，防止并发问题
            store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=self.raw_id)

            # 恢复库存：将出库数量加回到剩余库存中
            store_raw_detail.remaining_num -= self.sellback_num
            if store_raw_detail.remaining_num < 0:
                raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
            store_raw_detail.save()

            # 执行实际删除操作
            super().delete(*args, **kwargs)

    # 为批量删除也添加库存恢复逻辑
    def bulk_delete_with_stock_recovery(queryset):
        """批量删除出库明细并恢复库存的方法"""
        with transaction.atomic():
            # 遍历所有要删除的记录
            for detail in queryset:

                # 恢复库存
                store_raw_detail = StoreFinishedDetailModel.objects.select_for_update().get(id=detail.raw_id)
                store_raw_detail.remaining_num -= detail.sellback_num
                if store_raw_detail.remaining_num < 0:
                    raise ValidationError(f"物料库存已经小于0，不能进行此操作！")
                store_raw_detail.save()

            # 批量删除记录
            queryset.delete()




